diff options
386 files changed, 10341 insertions, 4815 deletions
diff --git a/Android.bp b/Android.bp index c0a70b99827e..2c550fdfb675 100644 --- a/Android.bp +++ b/Android.bp @@ -428,6 +428,15 @@ java_library { name: "framework-minus-apex-intdefs", defaults: ["framework-minus-apex-defaults"], plugins: ["intdef-annotation-processor"], + + // Errorprone and android lint will already run on framework-minus-apex, don't rerun them on + // the intdefs version in order to speed up the build. + errorprone: { + enabled: false, + }, + lint: { + enabled: false, + }, } // This "framework" module is NOT installed to the device. It's diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 1d92778a9117..9a4323a119f0 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -25,6 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} +ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES} diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java index 3781b6dcf162..03e5468a342a 100644 --- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -257,7 +257,7 @@ public class BlobStorePerfTests { runShellCommand(String.format( "cmd blob_store delete-blob --algo %s --digest %s --label %s --expiry %d --tag %s", blobHandle.algorithm, - Base64.getEncoder().encode(blobHandle.digest), + Base64.getEncoder().encodeToString(blobHandle.digest), blobHandle.label, blobHandle.expiryTimeMillis, blobHandle.tag)); diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml index 0f3a0681f85e..53e5d99409e2 100644 --- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml +++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml @@ -44,7 +44,7 @@ <option name="hidden-api-checks" value="false"/> <!-- Listener related args for collecting the traces and waiting for the device to stabilize. --> - <option name="device-listeners" value="android.device.collectors.PerfettoListener,android.device.collectors.SimpleperfListener" /> + <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener,android.device.collectors.SimpleperfListener" /> <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. --> <option name="instrumentation-arg" key="newRunListenerMode" value="true" /> @@ -58,7 +58,15 @@ <option name="instrumentation-arg" key="arguments" value="""" /> <option name="instrumentation-arg" key="events_to_record" value="instructions,cpu-cycles,raw-l3d-cache-refill,sched:sched_waking" /> <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger" /> - <option name="instrumentation-arg" key="symbols_to_report" value=""android::SurfaceFlinger::commit(long, long, long)"" /> + <option name="instrumentation-arg" key="symbols_to_report" value=""android::SurfaceFlinger::commit(;android::SurfaceFlinger::composite("" /> + + <!-- ProcLoadListener related arguments --> + <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run --> + <option name="instrumentation-arg" key="procload-collector:per_run" value="true" /> + <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" /> + <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" /> + <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" /> + </test> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java index f4d0c053fb66..45d164c96cd8 100644 --- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java +++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java @@ -16,6 +16,7 @@ package android.surfaceflinger; +import android.graphics.Bitmap; import android.graphics.Color; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; @@ -23,14 +24,19 @@ import android.view.SurfaceControl; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Random; + @LargeTest @RunWith(AndroidJUnit4.class) public class SurfaceFlingerPerfTest { @@ -48,19 +54,175 @@ public class SurfaceFlingerPerfTest { public void setup() { mActivityRule.getScenario().onActivity(activity -> mActivity = activity); } + + @After + public void teardown() { + mSurfaceControls.forEach(SurfaceControl::release); + mByfferTrackers.forEach(BufferFlinger::freeBuffers); + } + + + private ArrayList<BufferFlinger> mByfferTrackers = new ArrayList<>(); + private BufferFlinger createBufferTracker(int color) { + BufferFlinger bufferTracker = new BufferFlinger(BUFFER_COUNT, color); + mByfferTrackers.add(bufferTracker); + return bufferTracker; + } + + private ArrayList<SurfaceControl> mSurfaceControls = new ArrayList<>(); + private SurfaceControl createSurfaceControl() throws InterruptedException { + SurfaceControl sc = mActivity.createChildSurfaceControl(); + mSurfaceControls.add(sc); + return sc; + } + @Test - public void submitSingleBuffer() throws Exception { - SurfaceControl sc = mActivity.getChildSurfaceControl(); + public void singleBuffer() throws Exception { + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.GREEN); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - BufferFlinger bufferflinger = new BufferFlinger(BUFFER_COUNT, Color.GREEN); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + bufferTracker.addBuffer(t, sc); t.show(sc); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - bufferflinger.addBuffer(t, sc); + bufferTracker.addBuffer(t, sc); t.apply(); } - bufferflinger.freeBuffers(); } -} + static int getRandomColorComponent() { + return new Random().nextInt(155) + 100; + } + + @Test + public void multipleBuffers() throws Exception { + final int MAX_BUFFERS = 10; + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = 0; i < MAX_BUFFERS; i++) { + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.argb(getRandomColorComponent(), + getRandomColorComponent(), getRandomColorComponent(), + getRandomColorComponent())); + bufferTracker.addBuffer(t, sc); + t.setPosition(sc, i * 10, i * 10); + t.show(sc); + } + t.apply(true); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + for (int i = 0; i < MAX_BUFFERS; i++) { + mByfferTrackers.get(i).addBuffer(t, mSurfaceControls.get(i)); + } + t.apply(); + } + } + + @Test + public void multipleOpaqueBuffers() throws Exception { + final int MAX_BUFFERS = 10; + + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = 0; i < MAX_BUFFERS; i++) { + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.rgb(getRandomColorComponent(), + getRandomColorComponent(), getRandomColorComponent())); + bufferTracker.addBuffer(t, sc); + t.setOpaque(sc, true); + t.setPosition(sc, i * 10, i * 10); + t.show(sc); + } + t.apply(true); + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + for (int i = 0; i < MAX_BUFFERS; i++) { + mByfferTrackers.get(i).addBuffer(t, mSurfaceControls.get(i)); + } + t.apply(); + } + } + + @Test + public void geometryChanges() throws Exception { + final int MAX_POSITION = 10; + final float MAX_SCALE = 2.0f; + + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.GREEN); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + bufferTracker.addBuffer(t, sc); + t.show(sc).apply(true); + + int step = 0; + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + step = ++step % MAX_POSITION; + t.setPosition(sc, step, step); + float scale = ((step * MAX_SCALE) / MAX_POSITION) + 0.5f; + t.setScale(sc, scale, scale); + t.apply(); + } + } + + @Test + public void geometryWithBufferChanges() throws Exception { + final int MAX_POSITION = 10; + + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.GREEN); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + bufferTracker.addBuffer(t, sc); + t.show(sc).apply(true); + + int step = 0; + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + step = ++step % MAX_POSITION; + t.setPosition(sc, step, step); + float scale = ((step * 2.0f) / MAX_POSITION) + 0.5f; + t.setScale(sc, scale, scale); + bufferTracker.addBuffer(t, sc); + t.apply(); + } + } + + @Test + public void addRemoveLayers() throws Exception { + SurfaceControl sc = createSurfaceControl(); + BufferFlinger bufferTracker = createBufferTracker(Color.GREEN); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + SurfaceControl childSurfaceControl = new SurfaceControl.Builder() + .setName("childLayer").setBLASTLayer().build(); + bufferTracker.addBuffer(t, childSurfaceControl); + t.reparent(childSurfaceControl, sc); + t.apply(); + t.remove(childSurfaceControl).apply(); + } + } + + @Test + public void displayScreenshot() throws Exception { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + Bitmap screenshot = + InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot(); + screenshot.recycle(); + } + } + + @Test + public void layerScreenshot() throws Exception { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + Bitmap screenshot = + InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot( + mActivity.getWindow()); + screenshot.recycle(); + } + } + +} diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java index a9b2a3118bc1..832a0cd1e917 100644 --- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java +++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java @@ -43,7 +43,7 @@ public class SurfaceFlingerTestActivity extends Activity { setContentView(mTestSurfaceView); } - public SurfaceControl getChildSurfaceControl() throws InterruptedException { + public SurfaceControl createChildSurfaceControl() throws InterruptedException { return mTestSurfaceView.getChildSurfaceControlHelper(); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index f49cdbf403f0..ab0ac5aa8856 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1312,6 +1312,9 @@ public class JobInfo implements Parcelable { * Calling this method will override any requirements previously defined * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only * want to call one of these methods. + * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link JobScheduler} may try to shift the execution of jobs requiring + * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network. * <p class="note"> * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the @@ -1742,6 +1745,7 @@ public class JobInfo implements Parcelable { * <li>Bypass Doze, app standby, and battery saver network restrictions</li> * <li>Be less likely to be killed than regular jobs</li> * <li>Be subject to background location throttling</li> + * <li>Be exempt from delay to optimize job execution</li> * </ol> * * <p> diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 632ecb2c0381..dfdb29091ad9 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -45,8 +45,23 @@ import java.util.List; * </p> * <p> * The framework will be intelligent about when it executes jobs, and attempt to batch - * and defer them as much as possible. Typically if you don't specify a deadline on a job, it + * and defer them as much as possible. Typically, if you don't specify a deadline on a job, it * can be run at any moment depending on the current state of the JobScheduler's internal queue. + * </p> + * <p> + * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * JobScheduler may try to optimize job execution by shifting execution to times with more available + * system resources in order to lower user impact. Factors in system health include sufficient + * battery, idle, charging, and access to an un-metered network. Jobs will initially be treated as + * if they have all these requirements, but as their deadlines approach, restrictions will become + * less strict. Requested requirements will not be affected by this change. + * </p> + * + * {@see android.app.job.JobInfo.Builder#setRequiresBatteryNotLow(boolean)} + * {@see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)} + * {@see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)} + * {@see android.app.job.JobInfo.Builder#setRequiredNetworkType(int)} + * * <p> * While a job is running, the system holds a wakelock on behalf of your app. For this reason, * you do not need to take any action to guarantee that the device stays awake for the diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 448a808ff52f..e7f03279e1b0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -43,6 +43,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.net.Uri; +import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.Binder; import android.os.Handler; @@ -163,6 +164,7 @@ public class InternalResourceService extends SystemService { @GuardedBy("mLock") private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>(); + private volatile boolean mHasBattery = true; private volatile boolean mIsEnabled; private volatile int mBootPhase; private volatile boolean mExemptListLoaded; @@ -204,6 +206,15 @@ public class InternalResourceService extends SystemService { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { + case Intent.ACTION_BATTERY_CHANGED: { + final boolean hasBattery = + intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, mHasBattery); + if (mHasBattery != hasBattery) { + mHasBattery = hasBattery; + mConfigObserver.updateEnabledStatus(); + } + } + break; case Intent.ACTION_BATTERY_LEVEL_CHANGED: onBatteryLevelChanged(); break; @@ -713,6 +724,12 @@ public class InternalResourceService extends SystemService { return packages; } + private boolean isTareSupported() { + // TARE is presently designed for devices with batteries. Don't enable it on + // battery-less devices for now. + return mHasBattery; + } + @GuardedBy("mLock") private void loadInstalledPackageListLocked() { mPkgCache.clear(); @@ -731,6 +748,7 @@ public class InternalResourceService extends SystemService { private void registerListeners() { final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); @@ -765,6 +783,7 @@ public class InternalResourceService extends SystemService { return; } synchronized (mLock) { + mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); loadInstalledPackageListLocked(); final boolean isFirstSetup = !mScribe.recordExists(); if (isFirstSetup) { @@ -803,10 +822,7 @@ public class InternalResourceService extends SystemService { if (mBootPhase < PHASE_THIRD_PARTY_APPS_CAN_START || !mIsEnabled) { return; } - synchronized (mLock) { - mHandler.post(this::setupHeavyWork); - mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); - } + mHandler.post(this::setupHeavyWork); } private void onBootPhaseBootCompleted() { @@ -985,7 +1001,7 @@ public class InternalResourceService extends SystemService { @Override public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName, @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) { - if (isSystem(userId, pkgName)) { + if (!isTareSupported() || isSystem(userId, pkgName)) { // The system's affordability never changes. return; } @@ -1008,6 +1024,9 @@ public class InternalResourceService extends SystemService { @Override public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener) { + if (!isTareSupported()) { + return; + } mStateChangeListeners.add(listener); } @@ -1180,11 +1199,10 @@ public class InternalResourceService extends SystemService { private void updateEnabledStatus() { // User setting should override DeviceConfig setting. - // NOTE: There's currently no way for a user to reset the value (via UI), so if a user - // manually toggles TARE via UI, we'll always defer to the user's current setting final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE, KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1); - final boolean isTareEnabled = Settings.Global.getInt(mContentResolver, + final boolean isTareEnabled = isTareSupported() + && Settings.Global.getInt(mContentResolver, Settings.Global.ENABLE_TARE, isTareEnabledDC ? 1 : 0) == 1; if (mIsEnabled != isTareEnabled) { mIsEnabled = isTareEnabled; @@ -1268,6 +1286,10 @@ public class InternalResourceService extends SystemService { } private void dumpInternal(final IndentingPrintWriter pw, final boolean dumpAll) { + if (!isTareSupported()) { + pw.print("Unsupported by device"); + return; + } synchronized (mLock) { pw.print("Is enabled: "); pw.println(mIsEnabled); diff --git a/cmds/abx/Android.bp b/cmds/abx/Android.bp index 50a0b75b3276..a832dea20673 100644 --- a/cmds/abx/Android.bp +++ b/cmds/abx/Android.bp @@ -1,4 +1,3 @@ - package { default_applicable_licenses: ["frameworks_base_cmds_abx_license"], } @@ -18,7 +17,7 @@ license { java_binary { name: "abx", - wrapper: "abx", + wrapper: "abx.sh", srcs: ["**/*.java"], required: [ "abx2xml", @@ -28,10 +27,10 @@ java_binary { sh_binary { name: "abx2xml", - src: "abx2xml", + src: "abx2xml.sh", } sh_binary { name: "xml2abx", - src: "xml2abx", + src: "xml2abx.sh", } diff --git a/cmds/abx/abx b/cmds/abx/abx.sh index 0a9362d64398..0a9362d64398 100755 --- a/cmds/abx/abx +++ b/cmds/abx/abx.sh diff --git a/cmds/abx/abx2xml b/cmds/abx/abx2xml.sh index 0a9362d64398..0a9362d64398 100755 --- a/cmds/abx/abx2xml +++ b/cmds/abx/abx2xml.sh diff --git a/cmds/abx/xml2abx b/cmds/abx/xml2abx.sh index 0a9362d64398..0a9362d64398 100755 --- a/cmds/abx/xml2abx +++ b/cmds/abx/xml2abx.sh diff --git a/cmds/bmgr/Android.bp b/cmds/bmgr/Android.bp index 14beb5561dba..a85669a20e99 100644 --- a/cmds/bmgr/Android.bp +++ b/cmds/bmgr/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "bmgr", - wrapper: "bmgr", + wrapper: "bmgr.sh", srcs: ["**/*.java"], } diff --git a/cmds/bmgr/bmgr b/cmds/bmgr/bmgr.sh index b068d10dcbcf..b068d10dcbcf 100755 --- a/cmds/bmgr/bmgr +++ b/cmds/bmgr/bmgr.sh diff --git a/cmds/bu/Android.bp b/cmds/bu/Android.bp index 5b4ec3197cdd..b61a7a6e6429 100644 --- a/cmds/bu/Android.bp +++ b/cmds/bu/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "bu", - wrapper: "bu", + wrapper: "bu.sh", srcs: ["**/*.java"], } diff --git a/cmds/bu/bu b/cmds/bu/bu.sh index e50b53da4325..e50b53da4325 100755 --- a/cmds/bu/bu +++ b/cmds/bu/bu.sh diff --git a/cmds/content/Android.bp b/cmds/content/Android.bp index c70d01ec8f0b..0dd0f034e405 100644 --- a/cmds/content/Android.bp +++ b/cmds/content/Android.bp @@ -19,6 +19,6 @@ license { java_binary { name: "content", - wrapper: "content", + wrapper: "content.sh", srcs: ["**/*.java"], } diff --git a/cmds/content/content b/cmds/content/content.sh index 91f2dfb16ac9..91f2dfb16ac9 100755 --- a/cmds/content/content +++ b/cmds/content/content.sh diff --git a/cmds/device_config/Android.bp b/cmds/device_config/Android.bp index 69572d8690c4..83b2730c874f 100644 --- a/cmds/device_config/Android.bp +++ b/cmds/device_config/Android.bp @@ -14,5 +14,5 @@ package { sh_binary { name: "device_config", - src: "device_config", + src: "device_config.sh", } diff --git a/cmds/device_config/device_config b/cmds/device_config/device_config.sh index a949bd528263..a949bd528263 100755 --- a/cmds/device_config/device_config +++ b/cmds/device_config/device_config.sh diff --git a/cmds/dpm/Android.bp b/cmds/dpm/Android.bp index 6819d0982d2c..29bee4958be6 100644 --- a/cmds/dpm/Android.bp +++ b/cmds/dpm/Android.bp @@ -20,5 +20,5 @@ license { sh_binary { name: "dpm", - src: "dpm", + src: "dpm.sh", } diff --git a/cmds/dpm/dpm b/cmds/dpm/dpm.sh index 784db5b352a9..784db5b352a9 100755 --- a/cmds/dpm/dpm +++ b/cmds/dpm/dpm.sh diff --git a/cmds/hid/Android.bp b/cmds/hid/Android.bp index 295c71c65368..a6e27698e36c 100644 --- a/cmds/hid/Android.bp +++ b/cmds/hid/Android.bp @@ -20,7 +20,7 @@ license { java_binary { name: "hid", - wrapper: "hid", + wrapper: "hid.sh", srcs: ["**/*.java"], required: ["libhidcommand_jni"], } diff --git a/cmds/hid/hid b/cmds/hid/hid.sh index 43c76345c9e1..43c76345c9e1 100755 --- a/cmds/hid/hid +++ b/cmds/hid/hid.sh diff --git a/cmds/ime/Android.bp b/cmds/ime/Android.bp index 6dd3ba1ca7ef..5f54ffacbb00 100644 --- a/cmds/ime/Android.bp +++ b/cmds/ime/Android.bp @@ -20,5 +20,5 @@ license { sh_binary { name: "ime", - src: "ime", + src: "ime.sh", } diff --git a/cmds/ime/ime b/cmds/ime/ime.sh index 7d2f72f74010..7d2f72f74010 100755 --- a/cmds/ime/ime +++ b/cmds/ime/ime.sh diff --git a/cmds/input/Android.bp b/cmds/input/Android.bp index 2e30176971d7..8f44f3e34ef7 100644 --- a/cmds/input/Android.bp +++ b/cmds/input/Android.bp @@ -20,5 +20,5 @@ license { sh_binary { name: "input", - src: "input", + src: "input.sh", } diff --git a/cmds/input/input b/cmds/input/input.sh index d7d041431b49..d7d041431b49 100755 --- a/cmds/input/input +++ b/cmds/input/input.sh diff --git a/cmds/locksettings/Android.bp b/cmds/locksettings/Android.bp index 3869c8fcdabc..5ee582450361 100644 --- a/cmds/locksettings/Android.bp +++ b/cmds/locksettings/Android.bp @@ -23,6 +23,6 @@ package { java_binary { name: "locksettings", - wrapper: "locksettings", + wrapper: "locksettings.sh", srcs: ["**/*.java"], } diff --git a/cmds/locksettings/locksettings b/cmds/locksettings/locksettings.sh index 0ef4fa9f6771..0ef4fa9f6771 100755 --- a/cmds/locksettings/locksettings +++ b/cmds/locksettings/locksettings.sh diff --git a/cmds/pm/Android.bp b/cmds/pm/Android.bp index 847dbabdf4c0..0e61a9e35d5e 100644 --- a/cmds/pm/Android.bp +++ b/cmds/pm/Android.bp @@ -20,5 +20,5 @@ license { sh_binary { name: "pm", - src: "pm", + src: "pm.sh", } diff --git a/cmds/pm/pm b/cmds/pm/pm.sh index 4d1f94554a78..4d1f94554a78 100755 --- a/cmds/pm/pm +++ b/cmds/pm/pm.sh diff --git a/cmds/requestsync/Android.bp b/cmds/requestsync/Android.bp index 57e8dd355b26..8718f79c0d70 100644 --- a/cmds/requestsync/Android.bp +++ b/cmds/requestsync/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "requestsync", - wrapper: "requestsync", + wrapper: "requestsync.sh", srcs: ["**/*.java"], } diff --git a/cmds/requestsync/requestsync b/cmds/requestsync/requestsync.sh index 2d5d0e41705a..2d5d0e41705a 100755 --- a/cmds/requestsync/requestsync +++ b/cmds/requestsync/requestsync.sh diff --git a/cmds/settings/Android.bp b/cmds/settings/Android.bp index cc730062f9df..8180fd6cab61 100644 --- a/cmds/settings/Android.bp +++ b/cmds/settings/Android.bp @@ -14,5 +14,5 @@ package { sh_binary { name: "settings", - src: "settings", + src: "settings.sh", } diff --git a/cmds/settings/settings b/cmds/settings/settings.sh index d41ccc62811a..d41ccc62811a 100755 --- a/cmds/settings/settings +++ b/cmds/settings/settings.sh diff --git a/cmds/sm/Android.bp b/cmds/sm/Android.bp index ecfacaeecfd9..403022a2d2ae 100644 --- a/cmds/sm/Android.bp +++ b/cmds/sm/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "sm", - wrapper: "sm", + wrapper: "sm.sh", srcs: ["**/*.java"], } diff --git a/cmds/sm/sm b/cmds/sm/sm.sh index 30eae00a990d..30eae00a990d 100755 --- a/cmds/sm/sm +++ b/cmds/sm/sm.sh diff --git a/cmds/svc/Android.bp b/cmds/svc/Android.bp index 41a3ebd2f514..a2460875f28e 100644 --- a/cmds/svc/Android.bp +++ b/cmds/svc/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "svc", - wrapper: "svc", + wrapper: "svc.sh", srcs: ["**/*.java"], } diff --git a/cmds/svc/svc b/cmds/svc/svc.sh index a2c9de32b3d0..a2c9de32b3d0 100755 --- a/cmds/svc/svc +++ b/cmds/svc/svc.sh diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp index 4da79c54477b..be027105ae98 100644 --- a/cmds/telecom/Android.bp +++ b/cmds/telecom/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "telecom", - wrapper: "telecom", + wrapper: "telecom.sh", srcs: ["**/*.java"], } diff --git a/cmds/telecom/telecom b/cmds/telecom/telecom.sh index a19036bf5bd8..a19036bf5bd8 100755 --- a/cmds/telecom/telecom +++ b/cmds/telecom/telecom.sh diff --git a/cmds/uiautomator/cmds/uiautomator/Android.bp b/cmds/uiautomator/cmds/uiautomator/Android.bp index 56e2e70958fa..51323860725b 100644 --- a/cmds/uiautomator/cmds/uiautomator/Android.bp +++ b/cmds/uiautomator/cmds/uiautomator/Android.bp @@ -25,7 +25,7 @@ package { java_binary { name: "uiautomator", - wrapper: "uiautomator", + wrapper: "uiautomator.sh", srcs: ["src/**/*.java"], static_libs: ["uiautomator.core"], } diff --git a/cmds/uiautomator/cmds/uiautomator/uiautomator b/cmds/uiautomator/cmds/uiautomator/uiautomator.sh index 889c2b524dfd..889c2b524dfd 100755 --- a/cmds/uiautomator/cmds/uiautomator/uiautomator +++ b/cmds/uiautomator/cmds/uiautomator/uiautomator.sh diff --git a/cmds/uinput/Android.bp b/cmds/uinput/Android.bp index 260cfc781ebc..4b08d968ac4a 100644 --- a/cmds/uinput/Android.bp +++ b/cmds/uinput/Android.bp @@ -20,9 +20,10 @@ license { java_binary { name: "uinput", - wrapper: "uinput", - srcs: ["**/*.java", - ":uinputcommand_aidl" + wrapper: "uinput.sh", + srcs: [ + "**/*.java", + ":uinputcommand_aidl", ], required: ["libuinputcommand_jni"], } diff --git a/cmds/uinput/uinput b/cmds/uinput/uinput.sh index ab2770ee2043..ab2770ee2043 100755 --- a/cmds/uinput/uinput +++ b/cmds/uinput/uinput.sh diff --git a/cmds/vr/Android.bp b/cmds/vr/Android.bp index 893649155fd8..61795b4cc052 100644 --- a/cmds/vr/Android.bp +++ b/cmds/vr/Android.bp @@ -20,6 +20,6 @@ license { java_binary { name: "vr", - wrapper: "vr", + wrapper: "vr.sh", srcs: ["**/*.java"], } diff --git a/cmds/vr/vr b/cmds/vr/vr.sh index dbde02a4ac7f..dbde02a4ac7f 100755 --- a/cmds/vr/vr +++ b/cmds/vr/vr.sh diff --git a/cmds/wm/Android.bp b/cmds/wm/Android.bp index cf6b0193fe87..4d00f175211d 100644 --- a/cmds/wm/Android.bp +++ b/cmds/wm/Android.bp @@ -20,5 +20,5 @@ license { sh_binary { name: "wm", - src: "wm", + src: "wm.sh", } diff --git a/cmds/wm/wm b/cmds/wm/wm.sh index cb45be20c24a..cb45be20c24a 100755 --- a/cmds/wm/wm +++ b/cmds/wm/wm.sh diff --git a/core/api/current.txt b/core/api/current.txt index d5194529d981..a26ca4fceae9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32172,6 +32172,7 @@ package android.os { method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectCustomSlowCalls(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectDiskReads(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectDiskWrites(); + method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectExplicitGc(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectNetwork(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectResourceMismatches(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectUnbufferedIo(); @@ -32186,6 +32187,7 @@ package android.os { method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitDiskReads(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitDiskWrites(); + method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitExplicitGc(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitNetwork(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitResourceMismatches(); method @NonNull public android.os.StrictMode.ThreadPolicy.Builder permitUnbufferedIo(); @@ -32767,6 +32769,9 @@ package android.os.strictmode { public final class DiskWriteViolation extends android.os.strictmode.Violation { } + public final class ExplicitGcViolation extends android.os.strictmode.Violation { + } + public final class FileUriExposedViolation extends android.os.strictmode.Violation { } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 53014a3c580e..73c666446400 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -793,6 +793,7 @@ package android.content.pm { field public static final long FORCE_RESIZE_APP = 174042936L; // 0xa5faf38L field public static final long NEVER_SANDBOX_DISPLAY_APIS = 184838306L; // 0xb0468a2L field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L + field public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L; // 0xd0d1070L field public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // 0xabf9183L field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f; field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL @@ -1826,10 +1827,6 @@ package android.os { method public static void setViolationLogger(android.os.StrictMode.ViolationLogger); } - public static final class StrictMode.ThreadPolicy.Builder { - method @NonNull public android.os.StrictMode.ThreadPolicy.Builder detectExplicitGc(); - } - public static final class StrictMode.ViolationInfo implements android.os.Parcelable { ctor public StrictMode.ViolationInfo(android.os.Parcel); ctor public StrictMode.ViolationInfo(android.os.Parcel, boolean); @@ -2038,13 +2035,6 @@ package android.os.storage { } -package android.os.strictmode { - - public final class ExplicitGcViolation extends android.os.strictmode.Violation { - } - -} - package android.os.vibrator { public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment { @@ -3386,6 +3376,7 @@ package android.window { method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams); method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken); method public int describeContents(); + method @NonNull public android.window.WindowContainerTransaction removeTask(@NonNull android.window.WindowContainerToken); method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean); method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index b9ad595778e4..7215987b0d87 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -753,7 +753,7 @@ public final class NotificationChannel implements Parcelable { /** * Returns the vibration pattern for notifications posted to this channel. Will be ignored if - * vibration is not enabled ({@link #shouldVibrate()}. + * vibration is not enabled ({@link #shouldVibrate()}). */ public long[] getVibrationPattern() { return mVibration; diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 7014d6963538..a5a50d695793 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -43,6 +43,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.Executor; @@ -1103,7 +1104,10 @@ public final class VoiceInteractor { } try { boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands); - if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res); + if (DEBUG) { + Log.d(TAG, "supportsCommands: cmds=" + Arrays.toString(commands) + " res=" + + Arrays.toString(res)); + } return res; } catch (RemoteException e) { throw new RuntimeException("Voice interactor has died", e); diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java index 24b4f4b99ad0..63c9839a87e3 100644 --- a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java +++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java @@ -203,17 +203,14 @@ public final class PreferentialNetworkServiceConfig implements Parcelable { return mIsEnabled == that.mIsEnabled && mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection && mNetworkId == that.mNetworkId - && Objects.equals(mIncludedUids, that.mIncludedUids) - && Objects.equals(mExcludedUids, that.mExcludedUids); + && Arrays.equals(mIncludedUids, that.mIncludedUids) + && Arrays.equals(mExcludedUids, that.mExcludedUids); } @Override public int hashCode() { - return ((Objects.hashCode(mIsEnabled) * 17) - + (Objects.hashCode(mAllowFallbackToDefaultConnection) * 19) - + (Objects.hashCode(mIncludedUids) * 23) - + (Objects.hashCode(mExcludedUids) * 29) - + mNetworkId * 31); + return Objects.hash(mIsEnabled, mAllowFallbackToDefaultConnection, + Arrays.hashCode(mIncludedUids), Arrays.hashCode(mExcludedUids), mNetworkId); } /** diff --git a/core/java/android/app/ambientcontext/AmbientContextCallback.java b/core/java/android/app/ambientcontext/AmbientContextCallback.java new file mode 100644 index 000000000000..9133d7fe6634 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.annotation.NonNull; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Callback for listening to Ambient Context events and status changes. See {@link + * AmbientContextManager#registerObserver(AmbientContextEventRequest, AmbientContextCallback, + * Executor)} + * + * @hide + */ +public interface AmbientContextCallback { + /** + * Called when AmbientContextManager service detects events. + * + * @param events a list of detected events. + */ + void onEvents(@NonNull List<AmbientContextEvent> events); + + /** + * Called with a statusCode when + * {@link AmbientContextManager#registerObserver(AmbientContextEventRequest, + * Executor, AmbientContextCallback)} completes, to indicate if the registration is successful + * + * @param statusCode the status of the service. + */ + void onRegistrationComplete(@NonNull @AmbientContextManager.StatusCode int statusCode); +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index 11e695ad7fad..af48fde6423a 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -59,11 +59,21 @@ public final class AmbientContextEvent implements Parcelable { */ public static final int EVENT_SNORE = 2; + /** + * The integer indicating a double-tap event was detected. + * For detecting this event type, there's no specific consent activity to request access, but + * the consent is implied through the double tap toggle in the Settings app. + * + * @hide + */ + public static final int EVENT_BACK_DOUBLE_TAP = 3; + /** @hide */ @IntDef(prefix = { "EVENT_" }, value = { EVENT_UNKNOWN, EVENT_COUGH, EVENT_SNORE, + EVENT_BACK_DOUBLE_TAP, }) public @interface EventCode {} /** The integer indicating an unknown level. */ @@ -150,7 +160,8 @@ public final class AmbientContextEvent implements Parcelable { @IntDef(prefix = "EVENT_", value = { EVENT_UNKNOWN, EVENT_COUGH, - EVENT_SNORE + EVENT_SNORE, + EVENT_BACK_DOUBLE_TAP }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -166,6 +177,8 @@ public final class AmbientContextEvent implements Parcelable { return "EVENT_COUGH"; case EVENT_SNORE: return "EVENT_SNORE"; + case EVENT_BACK_DOUBLE_TAP: + return "EVENT_BACK_DOUBLE_TAP"; default: return Integer.toHexString(value); } } @@ -478,10 +491,10 @@ public final class AmbientContextEvent implements Parcelable { } @DataClass.Generated( - time = 1642040319323L, + time = 1659950304931L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", - inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index 308c5edfd0f4..9cb1a204b312 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -282,7 +282,7 @@ public final class AmbientContextManager { Preconditions.checkArgument(!resultPendingIntent.isImmutable()); try { RemoteCallback callback = new RemoteCallback(result -> { - int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); + int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); final long identity = Binder.clearCallingIdentity(); try { executor.execute(() -> statusConsumer.accept(statusCode)); @@ -297,6 +297,72 @@ public final class AmbientContextManager { } /** + * Allows app to register as a {@link AmbientContextEvent} observer. Same as {@link + * #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)}, + * but use {@link AmbientContextCallback} instead of {@link PendingIntent} as a callback on + * detected events. + * Registering another observer from the same package that has already been + * registered will override the previous observer. If the same app previously calls + * {@link #registerObserver(AmbientContextEventRequest, AmbientContextCallback, Executor)}, + * and now calls + * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)}, + * the previous observer will be replaced with the new observer with the PendingIntent callback. + * Or vice versa. + * + * When the registration completes, a status will be returned to client through + * {@link AmbientContextCallback#onRegistrationComplete(int)}. + * If the AmbientContextManager service is not enabled yet, or the underlying detection service + * is not running yet, {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} will be + * returned, and the detection won't be really started. + * If the underlying detection service feature is not enabled, or the requested event type is + * not enabled yet, {@link AmbientContextManager#STATUS_NOT_SUPPORTED} will be returned, and the + * detection won't be really started. + * If there is no user consent, {@link AmbientContextManager#STATUS_ACCESS_DENIED} will be + * returned, and the detection won't be really started. + * Otherwise, it will try to start the detection. And if it starts successfully, it will return + * {@link AmbientContextManager#STATUS_SUCCESS}. If it fails to start the detection, then + * it will return {@link AmbientContextManager#STATUS_SERVICE_UNAVAILABLE} + * After registerObserver succeeds and when the service detects an event, the service will + * trigger {@link AmbientContextCallback#onEvents(List)}. + * + * @hide + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void registerObserver( + @NonNull AmbientContextEventRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull AmbientContextCallback ambientContextCallback) { + try { + IAmbientContextObserver observer = new IAmbientContextObserver.Stub() { + @Override + public void onEvents(List<AmbientContextEvent> events) throws RemoteException { + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> ambientContextCallback.onEvents(events)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onRegistrationComplete(int statusCode) throws RemoteException { + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute( + () -> ambientContextCallback.onRegistrationComplete(statusCode)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }; + + mService.registerObserverWithCallback(request, mContext.getPackageName(), observer); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an * observer that was already unregistered or never registered will have no effect. */ diff --git a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl index 0d9ecfdfe8f6..8f06e76db559 100644 --- a/core/java/android/app/ambientcontext/IAmbientContextManager.aidl +++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl @@ -18,6 +18,7 @@ package android.app.ambientcontext; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.IAmbientContextObserver; import android.os.RemoteCallback; /** @@ -29,6 +30,11 @@ interface IAmbientContextManager { void registerObserver(in AmbientContextEventRequest request, in PendingIntent resultPendingIntent, in RemoteCallback statusCallback); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)") + void registerObserverWithCallback(in AmbientContextEventRequest request, + String packageName, + in IAmbientContextObserver observer); void unregisterObserver(in String callingPackage); void queryServiceStatus(in int[] eventTypes, in String callingPackage, in RemoteCallback statusCallback); diff --git a/core/java/android/app/ambientcontext/IAmbientContextObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextObserver.aidl new file mode 100644 index 000000000000..529e2fea7edc --- /dev/null +++ b/core/java/android/app/ambientcontext/IAmbientContextObserver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ambientcontext; + +import android.app.ambientcontext.AmbientContextEvent; + +/** + * Callback interface of AmbientContextManager. + * + * @hide + */ +oneway interface IAmbientContextObserver { + void onEvents(in List<AmbientContextEvent> events); + void onRegistrationComplete(in int statusCode); +} + diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index e6091f04a72a..2934cd27da19 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -204,9 +204,10 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { @Override public int hashCode() { - return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask, - mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength, - mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder); + return Objects.hash(mNamePattern, mScanFilter, Arrays.hashCode(mRawDataFilter), + Arrays.hashCode(mRawDataFilterMask), mRenamePrefix, mRenameSuffix, + mRenameBytesFrom, mRenameBytesLength, mRenameNameFrom, mRenameNameLength, + mRenameBytesReverseOrder); } @Override diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 3f2fa2188d24..b0c6cbced4f9 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -495,14 +495,9 @@ public final class AttributionSource implements Parcelable { @Override public int hashCode() { - int _hash = 1; - _hash = 31 * _hash + mAttributionSourceState.uid; - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token); - _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions); - _hash = 31 * _hash + Objects.hashCode(getNext()); - return _hash; + return Objects.hash(mAttributionSourceState.uid, mAttributionSourceState.packageName, + mAttributionSourceState.attributionTag, mAttributionSourceState.token, + Arrays.hashCode(mAttributionSourceState.renouncedPermissions), getNext()); } @Override diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index e92be7cb64e2..26c947b5f916 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1120,6 +1120,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; /** + * Overrides the min aspect ratio restriction in portrait fullscreen in order to use all + * available screen space. + * @hide + */ + @ChangeId + @Disabled + @Overridable + @TestApi + public static final long OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN = 218959984L; + + /** * Compares activity window layout min width/height with require space for multi window to * determine if it can be put into multi window mode. */ diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index f780f815fa89..f8c49744d834 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1723,7 +1723,8 @@ public class LauncherApps { public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing) throws RemoteException { if (DEBUG) { - Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames); + Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + + Arrays.toString(packageNames)); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { @@ -1736,7 +1737,8 @@ public class LauncherApps { public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing) throws RemoteException { if (DEBUG) { - Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames); + Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + + Arrays.toString(packageNames)); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { @@ -1750,7 +1752,8 @@ public class LauncherApps { Bundle launcherExtras) throws RemoteException { if (DEBUG) { - Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames); + Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + + Arrays.toString(packageNames)); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { @@ -1763,7 +1766,8 @@ public class LauncherApps { public void onPackagesUnsuspended(UserHandle user, String[] packageNames) throws RemoteException { if (DEBUG) { - Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames); + Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + + Arrays.toString(packageNames)); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 52774e354c90..1f83d7532f04 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -53,6 +53,7 @@ import com.android.internal.util.Preconditions; 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.HashMap; import java.util.List; @@ -2617,7 +2618,7 @@ public final class ShortcutInfo implements Parcelable { addIndentOrComma(sb, indent); sb.append("persons="); - sb.append(mPersons); + sb.append(Arrays.toString(mPersons)); addIndentOrComma(sb, indent); diff --git a/core/java/android/content/res/GradientColor.java b/core/java/android/content/res/GradientColor.java index 35ad5033e395..7bc551d7f49e 100644 --- a/core/java/android/content/res/GradientColor.java +++ b/core/java/android/content/res/GradientColor.java @@ -22,13 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo.Config; import android.content.res.Resources.Theme; - -import com.android.internal.R; -import com.android.internal.util.GrowingArrayUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.graphics.LinearGradient; import android.graphics.RadialGradient; import android.graphics.Shader; @@ -38,9 +31,16 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import com.android.internal.R; +import com.android.internal.util.GrowingArrayUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; /** * Lets you define a gradient color, which is used inside @@ -466,7 +466,7 @@ public class GradientColor extends ComplexColor { } if (tempColors.length < 2) { Log.w(TAG, "<gradient> tag requires 2 color values specified!" + tempColors.length - + " " + tempColors); + + " " + Arrays.toString(tempColors)); } if (mGradientType == GradientDrawable.LINEAR_GRADIENT) { diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl index 5bdd96677da5..1333f0da725f 100644 --- a/core/java/android/hardware/HardwareBuffer.aidl +++ b/core/java/android/hardware/HardwareBuffer.aidl @@ -16,4 +16,4 @@ package android.hardware; -parcelable HardwareBuffer; +@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h"; diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl index 55cab52fc4f7..2e8e763b0ee4 100644 --- a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl +++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl @@ -23,5 +23,8 @@ package android.hardware.biometrics; * @hide */ oneway interface IBiometricContextListener { - void onDozeChanged(boolean isDozing); + // Called when doze or awake (screen on) status changes. + // These may be called while the device is still transitioning to the new state + // (i.e. about to become awake or enter doze) + void onDozeChanged(boolean isDozing, boolean isAwake); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index c614cdbc3199..72d812269374 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -1433,10 +1433,17 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } @NonNull - private static float[] createEnrollStageThresholds(@NonNull Context context) { + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + private float[] createEnrollStageThresholds(@NonNull Context context) { // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config - final String[] enrollStageThresholdStrings = context.getResources().getStringArray( - com.android.internal.R.array.config_udfps_enroll_stage_thresholds); + final String[] enrollStageThresholdStrings; + if (isPowerbuttonFps()) { + enrollStageThresholdStrings = context.getResources().getStringArray( + com.android.internal.R.array.config_sfps_enroll_stage_thresholds); + } else { + enrollStageThresholdStrings = context.getResources().getStringArray( + com.android.internal.R.array.config_udfps_enroll_stage_thresholds); + } final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length]; for (int i = 0; i < enrollStageThresholds.length; i++) { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 6300a12c3137..a13eadaefe5a 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -491,8 +491,12 @@ public final class ProgramSelector implements Parcelable { public String toString() { StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType) .append(", primary=").append(mPrimaryId); - if (mSecondaryIds.length > 0) sb.append(", secondary=").append(mSecondaryIds); - if (mVendorIds.length > 0) sb.append(", vendor=").append(mVendorIds); + if (mSecondaryIds.length > 0) { + sb.append(", secondary=").append(Arrays.toString(mSecondaryIds)); + } + if (mVendorIds.length > 0) { + sb.append(", vendor=").append(Arrays.toString(mVendorIds)); + } sb.append(")"); return sb.toString(); } diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 8180caa1557b..43341166d720 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -505,7 +505,8 @@ public class RadioManager { public int hashCode() { return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, mSerial, mNumTuners, mNumAudioSources, mIsInitializationRequired, - mIsCaptureSupported, mBands, mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); + mIsCaptureSupported, Arrays.hashCode(mBands), mIsBgScanSupported, + mDabFrequencyTable, mVendorInfo); } @Override @@ -525,7 +526,7 @@ public class RadioManager { if (mNumAudioSources != other.mNumAudioSources) return false; if (mIsInitializationRequired != other.mIsInitializationRequired) return false; if (mIsCaptureSupported != other.mIsCaptureSupported) return false; - if (!Objects.equals(mBands, other.mBands)) return false; + if (!Arrays.equals(mBands, other.mBands)) return false; if (mIsBgScanSupported != other.mIsBgScanSupported) return false; if (!Objects.equals(mDabFrequencyTable, other.mDabFrequencyTable)) return false; if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java index a19eb5668d55..8644d9103dcb 100644 --- a/core/java/android/metrics/LogMaker.java +++ b/core/java/android/metrics/LogMaker.java @@ -23,6 +23,7 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import java.util.Arrays; /** @@ -395,7 +396,7 @@ public class LogMaker { out[i * 2] = entries.keyAt(i); out[i * 2 + 1] = entries.valueAt(i); } - int size = out.toString().getBytes().length; + int size = Arrays.toString(out).getBytes().length; if (size > MAX_SERIALIZED_SIZE) { Log.i(TAG, "Log line too long, did not emit: " + size + " bytes."); throw new RuntimeException(); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index da206268917e..0ad596b682ce 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1767,6 +1767,49 @@ public abstract class BatteryStats { } /** + * Measured energy delta from the previous reading. + */ + public static final class MeasuredEnergyDetails { + /** + * Description of the energy consumer, such as CPU, DISPLAY etc + */ + public static final class EnergyConsumer { + /** + * See android.hardware.power.stats.EnergyConsumerType + */ + public int type; + /** + * Used when there are multipe energy consumers of the same type, such + * as CPU clusters, multiple displays on foldable devices etc. + */ + public int ordinal; + /** + * Human-readable name of the energy consumer, e.g. "CPU" + */ + public String name; + } + public EnergyConsumer[] consumers; + public long[] chargeUC; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < consumers.length; i++) { + if (chargeUC[i] == POWER_DATA_UNAVAILABLE) { + continue; + } + if (sb.length() != 0) { + sb.append(' '); + } + sb.append(consumers[i].name); + sb.append('='); + sb.append(chargeUC[i]); + } + return sb.toString(); + } + } + + /** * Battery history record. */ public static final class HistoryItem { @@ -1886,6 +1929,7 @@ public abstract class BatteryStats { public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20; public static final int STATE2_CELLULAR_HIGH_TX_POWER_FLAG = 1 << 19; public static final int STATE2_USB_DATA_LINK_FLAG = 1 << 18; + public static final int STATE2_EXTENSIONS_FLAG = 1 << 17; public static final int MOST_INTERESTING_STATES2 = STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK @@ -1905,6 +1949,9 @@ public abstract class BatteryStats { // Non-null when there is more detailed information at this step. public HistoryStepDetails stepDetails; + // Non-null when there is measured energy information + public MeasuredEnergyDetails measuredEnergyDetails; + public static final int EVENT_FLAG_START = 0x8000; public static final int EVENT_FLAG_FINISH = 0x4000; @@ -2113,6 +2160,7 @@ public abstract class BatteryStats { eventCode = EVENT_NONE; eventTag = null; tagsFirstOccurrence = false; + measuredEnergyDetails = null; } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -2162,6 +2210,7 @@ public abstract class BatteryStats { } tagsFirstOccurrence = o.tagsFirstOccurrence; currentTime = o.currentTime; + measuredEnergyDetails = o.measuredEnergyDetails; } public boolean sameNonEvent(HistoryItem o) { @@ -6951,6 +7000,14 @@ public abstract class BatteryStats { item.append("\""); } } + if ((rec.states2 & HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) { + if (!checkin) { + item.append(" ext="); + if (rec.measuredEnergyDetails != null) { + item.append("E"); + } + } + } if (rec.eventCode != HistoryItem.EVENT_NONE) { item.append(checkin ? "," : " "); if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) { @@ -7075,6 +7132,25 @@ public abstract class BatteryStats { item.append("\n"); } } + if (rec.measuredEnergyDetails != null) { + if (!checkin) { + item.append(" Energy: "); + item.append(rec.measuredEnergyDetails); + item.append("\n"); + } else { + item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); + item.append(HISTORY_DATA); item.append(",0,XE"); + for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) { + if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) { + item.append(','); + item.append(rec.measuredEnergyDetails.consumers[i].name); + item.append('='); + item.append(rec.measuredEnergyDetails.chargeUC[i]); + } + } + item.append("\n"); + } + } oldState = rec.states; oldState2 = rec.states2; // Clear High Tx Power Flag for volta positioning diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 43a6be5df202..fb8f84acc8f3 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -469,7 +469,15 @@ public class GraphicsEnvironment { * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and * Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the * “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it - * forces a choice; otherwise ... + * forces a choice; + * - Workaround Note: ANGLE and Vulkan currently have issues with applications that use YUV + * target functionality. The ANGLE broadcast receiver code will apply a "deferlist" at + * the first boot of a newly-flashed device. However, there is a gap in time between + * when applications can start and when the deferlist is applied. For now, assume that + * if ANGLE is the system driver and Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS is + * empty, that the deferlist has not yet been applied. In this case, select the Legacy + * driver. + * otherwise ... * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ... * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy; * a.k.a. mAngleIsSystemDriver, which is set by the device’s “ro.hardware.egl” property) @@ -509,9 +517,16 @@ public class GraphicsEnvironment { final List<String> optInValues = getGlobalSettingsString( contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES); Log.v(TAG, "Currently set values for:"); - Log.v(TAG, " angle_gl_driver_selection_pkgs = " + optInPackages); + Log.v(TAG, " angle_gl_driver_selection_pkgs =" + optInPackages); Log.v(TAG, " angle_gl_driver_selection_values =" + optInValues); + // If ANGLE is the system driver AND the deferlist has not yet been applied, select the + // Legacy driver + if (mAngleIsSystemDriver && optInPackages.size() <= 1) { + Log.v(TAG, "Ignoring angle_gl_driver_selection_* until deferlist has been applied"); + return ANGLE_GL_DRIVER_TO_USE_LEGACY; + } + // Make sure we have good settings to use if (optInPackages.size() != optInValues.size()) { Log.w(TAG, diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index aefa5f827f4a..ceaf337b2122 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -182,7 +182,7 @@ public class Handler { * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}. + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. @@ -203,7 +203,7 @@ public class Handler { * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}. + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param callback The callback interface in which to handle messages, or null. * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for @@ -212,17 +212,25 @@ public class Handler { * @hide */ public Handler(@Nullable Callback callback, boolean async) { - this(getThreadLooper(), callback, async); - } + if (FIND_POTENTIAL_LEAKS) { + final Class<? extends Handler> klass = getClass(); + if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && + (klass.getModifiers() & Modifier.STATIC) == 0) { + Log.w(TAG, "The following Handler class should be static or leaks might occur: " + + klass.getCanonicalName()); + } + } - private static Looper getThreadLooper() { - final Looper looper = Looper.myLooper(); - if (looper == null) { + mLooper = Looper.myLooper(); + if (mLooper == null) { throw new RuntimeException( - "Can't create handler inside thread " + Thread.currentThread() - + " that has not called Looper.prepare()"); + "Can't create handler inside thread " + Thread.currentThread() + + " that has not called Looper.prepare()"); } - return looper; + mQueue = mLooper.mQueue; + mCallback = callback; + mAsynchronous = async; + mIsShared = false; } /** @@ -249,44 +257,14 @@ public class Handler { this(looper, callback, async, /* shared= */ false); } - /** - * Use the provided {@link Looper} instead of the default one and take a callback - * interface in which to handle messages. Also set whether the handler - * should be asynchronous. - * - * Handlers are synchronous by default unless this constructor is used to make - * one that is strictly asynchronous. - * - * Asynchronous messages represent interrupts or events that do not require global ordering - * with respect to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by conditions such as display vsync. - * - * @param looper The looper, must not be null. - * @param callback The callback interface in which to handle messages, or null. - * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for - * each {@link Message} that is sent to it or {@link Runnable} that is posted to - * it. - * @param shared Whether this Handler might be used by more than one client. A shared Handler - * applies some extra policy, such as disallowing the removal of all messages, - * in order to avoid one client affecting another's messages. - * @hide - */ + /** @hide */ public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async, boolean shared) { - if (FIND_POTENTIAL_LEAKS) { - final Class<? extends Handler> klass = getClass(); - if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) - && (klass.getModifiers() & Modifier.STATIC) == 0) { - Log.w(TAG, "The following Handler class should be static or leaks might occur: " - + klass.getCanonicalName()); - } - } mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; mIsShared = shared; - mClock = looper.getClock(); } /** @@ -724,14 +702,7 @@ public class Handler { if (delayMillis < 0) { delayMillis = 0; } - // mClock should theoretically never be null, but some tests create a mock handler that - // instantiates an instance where all members are null. Ideally we'd fix these tests to - // not rely on this but there are quite a lot at this point, so it's easier to just keep - // the existing behavior. - if (mClock == null) { - return false; - } - return sendMessageAtTime(msg, mClock.uptimeMillis() + delayMillis); + return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } /** @@ -924,7 +895,7 @@ public class Handler { } public final void dump(@NonNull Printer pw, @NonNull String prefix) { - pw.println(prefix + this + " @ " + mClock.uptimeMillis()); + pw.println(prefix + this + " @ " + SystemClock.uptimeMillis()); if (mLooper == null) { pw.println(prefix + "looper uninitialized"); } else { @@ -936,7 +907,7 @@ public class Handler { * @hide */ public final void dumpMine(@NonNull Printer pw, @NonNull String prefix) { - pw.println(prefix + this + " @ " + mClock.uptimeMillis()); + pw.println(prefix + this + " @ " + SystemClock.uptimeMillis()); if (mLooper == null) { pw.println(prefix + "looper uninitialized"); } else { @@ -993,7 +964,6 @@ public class Handler { @UnsupportedAppUsage final Callback mCallback; final boolean mAsynchronous; - final MessageQueue.Clock mClock; @UnsupportedAppUsage IMessenger mMessenger; @@ -1027,9 +997,9 @@ public class Handler { synchronized (this) { if (timeout > 0) { - final long expirationTime = handler.mClock.uptimeMillis() + timeout; + final long expirationTime = SystemClock.uptimeMillis() + timeout; while (!mDone) { - long delay = expirationTime - handler.mClock.uptimeMillis(); + long delay = expirationTime - SystemClock.uptimeMillis(); if (delay <= 0) { return false; // timeout } diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index f0fda15f6e90..a529ac6569bd 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -24,8 +24,6 @@ import android.util.Printer; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import java.util.Objects; - /** * Class used to run a message loop for a thread. Threads by default do * not have a message loop associated with them; to create one, call @@ -71,7 +69,7 @@ public final class Looper { // sThreadLocal.get() will return null unless you've called prepare(). @UnsupportedAppUsage - static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>(); + static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); @UnsupportedAppUsage private static Looper sMainLooper; // guarded by Looper.class private static Observer sObserver; @@ -79,7 +77,6 @@ public final class Looper { @UnsupportedAppUsage final MessageQueue mQueue; final Thread mThread; - final MessageQueue.Clock mClock; private boolean mInLoop; @UnsupportedAppUsage @@ -194,7 +191,7 @@ public final class Looper { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } - final long dispatchStart = needStartTime ? me.mClock.uptimeMillis() : 0; + final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; Object token = null; if (observer != null) { @@ -206,7 +203,7 @@ public final class Looper { if (observer != null) { observer.messageDispatched(token, msg); } - dispatchEnd = needEndTime ? me.mClock.uptimeMillis() : 0; + dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); @@ -328,13 +325,8 @@ public final class Looper { } private Looper(boolean quitAllowed) { - this(quitAllowed, SystemClock::uptimeMillis); - } - - private Looper(boolean quitAllowed, @NonNull MessageQueue.Clock clock) { - mQueue = new MessageQueue(quitAllowed, Objects.requireNonNull(clock)); + mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); - mClock = clock; } /** @@ -427,16 +419,6 @@ public final class Looper { } /** - * Gets the looper's clock. - * - * @return The looper's clock - * @hide - */ - public @NonNull MessageQueue.Clock getClock() { - return mClock; - } - - /** * Dumps the state of the looper for debugging purposes. * * @param pw A printer to receive the contents of the dump. diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index a8ed5ba99489..87c4f331e93f 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -19,7 +19,6 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; -import android.annotation.UptimeMillisLong; import android.compat.annotation.UnsupportedAppUsage; import android.util.Log; import android.util.Printer; @@ -30,7 +29,6 @@ import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Objects; /** * Low-level class holding the list of messages to be dispatched by a @@ -56,7 +54,6 @@ public final class MessageQueue { Message mMessages; @UnsupportedAppUsage private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); - private final Clock mClock; private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; private IdleHandler[] mPendingIdleHandlers; private boolean mQuitting; @@ -77,10 +74,9 @@ public final class MessageQueue { private native static boolean nativeIsPolling(long ptr); private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); - MessageQueue(boolean quitAllowed, @NonNull Clock clock) { + MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); - mClock = Objects.requireNonNull(clock); } @Override @@ -110,7 +106,7 @@ public final class MessageQueue { */ public boolean isIdle() { synchronized (this) { - final long now = mClock.uptimeMillis(); + final long now = SystemClock.uptimeMillis(); return mMessages == null || now < mMessages.when; } } @@ -340,7 +336,7 @@ public final class MessageQueue { synchronized (this) { // Try to retrieve the next message. Return if found. - final long now = mClock.uptimeMillis(); + final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { @@ -474,7 +470,7 @@ public final class MessageQueue { @UnsupportedAppUsage @TestApi public int postSyncBarrier() { - return postSyncBarrier(mClock.uptimeMillis()); + return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { @@ -776,6 +772,41 @@ public final class MessageQueue { } } + void removeEqualMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + + synchronized (this) { + Message p = mMessages; + + // Remove all messages at front. + while (p != null && p.target == h && p.callback == r + && (object == null || object.equals(p.obj))) { + Message n = p.next; + mMessages = n; + p.recycleUnchecked(); + p = n; + } + + // Remove all messages after front. + while (p != null) { + Message n = p.next; + if (n != null) { + if (n.target == h && n.callback == r + && (object == null || object.equals(n.obj))) { + Message nn = n.next; + n.recycleUnchecked(); + p.next = nn; + continue; + } + } + p = n; + } + } + } + + void removeCallbacksAndMessages(Handler h, Object object) { if (h == null) { return; @@ -853,7 +884,7 @@ public final class MessageQueue { } private void removeAllFutureMessagesLocked() { - final long now = mClock.uptimeMillis(); + final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { if (p.when > now) { @@ -882,7 +913,7 @@ public final class MessageQueue { void dump(Printer pw, String prefix, Handler h) { synchronized (this) { - long now = mClock.uptimeMillis(); + long now = SystemClock.uptimeMillis(); int n = 0; for (Message msg = mMessages; msg != null; msg = msg.next) { if (h == null || h == msg.target) { @@ -911,7 +942,7 @@ public final class MessageQueue { * Callback interface for discovering when a thread is going to block * waiting for more messages. */ - public interface IdleHandler { + public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false @@ -1010,17 +1041,4 @@ public final class MessageQueue { mListener = listener; } } - - /** - * Time supplier for MessageQueue and the things that interact with it (e.g. {@link Looper}). - * - * Intentionally replaceable for testing. - * - * @hide - */ - public interface Clock { - /** @see SystemClock#uptimeMillis */ - @UptimeMillisLong - long uptimeMillis(); - } } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 412a33a1c124..113a64048796 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -26,6 +26,9 @@ import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.IActivityManager; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; @@ -344,6 +347,13 @@ public final class StrictMode { public static final int NETWORK_POLICY_LOG = 1; /** {@hide} */ public static final int NETWORK_POLICY_REJECT = 2; + + /** + * Detect explicit calls to {@link Runtime#gc()}. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + static final long DETECT_EXPLICIT_GC = 3400644L; // TODO: wrap in some ImmutableHashMap thing. // Note: must be before static initialization of sVmPolicy. @@ -496,6 +506,7 @@ public final class StrictMode { * <p>As of the Gingerbread release this includes network and disk operations but will * likely expand in future releases. */ + @SuppressWarnings("AndroidFrameworkCompatChange") public @NonNull Builder detectAll() { detectDiskReads(); detectDiskWrites(); @@ -511,6 +522,9 @@ public final class StrictMode { if (targetSdk >= Build.VERSION_CODES.O) { detectUnbufferedIo(); } + if (CompatChanges.isChangeEnabled(DETECT_EXPLICIT_GC)) { + detectExplicitGc(); + } return this; } @@ -591,26 +605,16 @@ public final class StrictMode { } /** - * Detect explicit GC requests, i.e. calls to Runtime.gc(). - * - * @hide + * Detect calls to {@link Runtime#gc()}. */ - @TestApi public @NonNull Builder detectExplicitGc() { - // TODO(b/3400644): Un-hide this for next API update - // TODO(b/3400644): Un-hide ExplicitGcViolation for next API update - // TODO(b/3400644): Make DETECT_EXPLICIT_GC a @TestApi for next API update - // TODO(b/3400644): Call this from detectAll in next API update return enable(DETECT_THREAD_EXPLICIT_GC); } /** - * Disable detection of explicit GC requests, i.e. calls to Runtime.gc(). - * - * @hide + * Disable detection of calls to {@link Runtime#gc()}. */ public @NonNull Builder permitExplicitGc() { - // TODO(b/3400644): Un-hide this for next API update return disable(DETECT_THREAD_EXPLICIT_GC); } diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 06930bb32090..392bf4ec3ece 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -145,7 +145,8 @@ public final class VibrationAttributes implements Parcelable { */ @IntDef(prefix = { "FLAG_" }, flag = true, value = { FLAG_BYPASS_INTERRUPTION_POLICY, - FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF + FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF, + FLAG_INVALIDATE_SETTINGS_CACHE }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} @@ -182,7 +183,7 @@ public final class VibrationAttributes implements Parcelable { * * @hide */ - public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x3; + public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x4; /** * All flags supported by vibrator service, update it when adding new flag. diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 13b22d370030..a1ed2533f544 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Objects; import java.util.UUID; @@ -515,7 +516,7 @@ public final class IncrementalStorage { throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize); } if (hashingInfo.salt != null && hashingInfo.salt.length > 0) { - throw new IOException("Unsupported salt: " + hashingInfo.salt); + throw new IOException("Unsupported salt: " + Arrays.toString(hashingInfo.salt)); } if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) { throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); diff --git a/core/java/android/os/strictmode/ExplicitGcViolation.java b/core/java/android/os/strictmode/ExplicitGcViolation.java index 583ed1ab4505..c4ae82ddbc05 100644 --- a/core/java/android/os/strictmode/ExplicitGcViolation.java +++ b/core/java/android/os/strictmode/ExplicitGcViolation.java @@ -19,10 +19,7 @@ import android.annotation.TestApi; /** * See #{@link android.os.StrictMode.ThreadPolicy.Builder#detectExplicitGc()}. - * - * @hide */ -@TestApi public final class ExplicitGcViolation extends Violation { /** @hide */ public ExplicitGcViolation() { diff --git a/core/java/android/service/autofill/OptionalValidators.java b/core/java/android/service/autofill/OptionalValidators.java index 7189c886aa06..204353942b03 100644 --- a/core/java/android/service/autofill/OptionalValidators.java +++ b/core/java/android/service/autofill/OptionalValidators.java @@ -25,6 +25,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import java.util.Arrays; + /** * Compound validator that returns {@code true} on {@link #isValid(ValueFinder)} if any * of its subvalidators returns {@code true} as well. @@ -61,7 +63,8 @@ final class OptionalValidators extends InternalValidator { public String toString() { if (!sDebug) return super.toString(); - return new StringBuilder("OptionalValidators: [validators=").append(mValidators) + return new StringBuilder("OptionalValidators: [validators=") + .append(Arrays.toString(mValidators)) .append("]") .toString(); } diff --git a/core/java/android/service/autofill/RequiredValidators.java b/core/java/android/service/autofill/RequiredValidators.java index 619eba0a79ad..054582e01f71 100644 --- a/core/java/android/service/autofill/RequiredValidators.java +++ b/core/java/android/service/autofill/RequiredValidators.java @@ -25,6 +25,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; +import java.util.Arrays; + /** * Compound validator that only returns {@code true} on {@link #isValid(ValueFinder)} if all * of its subvalidators return {@code true} as well. @@ -60,7 +62,8 @@ final class RequiredValidators extends InternalValidator { public String toString() { if (!sDebug) return super.toString(); - return new StringBuilder("RequiredValidators: [validators=").append(mValidators) + return new StringBuilder("RequiredValidators: [validators=") + .append(Arrays.toString(mValidators)) .append("]") .toString(); } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index d08a67e68676..f0f16dbbb39e 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -75,6 +75,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -599,7 +600,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface - + " finished=" + finished + " selections=" + selections + + " finished=" + finished + " selections=" + Arrays.toString(selections) + " result=" + result); if (finished) { finishRequest(); diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 3bde32b1e5f2..3177c5c8509e 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; +import java.util.Arrays; /** * Speech synthesis request that writes the audio to a WAV file. @@ -152,8 +153,8 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { @Override public int audioAvailable(byte[] buffer, int offset, int length) { if (DBG) { - Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset - + "," + length + ")"); + Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + Arrays.toString(buffer) + + "," + offset + "," + length + ")"); } FileChannel fileChannel = null; synchronized (mStateLock) { diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index c0bc9912af3d..155f508f6242 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -23,6 +23,7 @@ import com.android.internal.util.ArrayUtils; import libcore.util.EmptyArray; +import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Map; @@ -231,7 +232,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { array[0] = array[1] = null; mTwiceBaseCacheSize--; if (DEBUG) { - Log.d(TAG, "Retrieving 2x cache " + mHashes + Log.d(TAG, "Retrieving 2x cache " + Arrays.toString(mHashes) + " now have " + mTwiceBaseCacheSize + " entries"); } return; @@ -258,7 +259,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { array[0] = array[1] = null; mBaseCacheSize--; if (DEBUG) { - Log.d(TAG, "Retrieving 1x cache " + mHashes + Log.d(TAG, "Retrieving 1x cache " + Arrays.toString(mHashes) + " now have " + mBaseCacheSize + " entries"); } return; @@ -295,8 +296,10 @@ public final class ArrayMap<K, V> implements Map<K, V> { } mTwiceBaseCache = array; mTwiceBaseCacheSize++; - if (DEBUG) Log.d(TAG, "Storing 2x cache " + array - + " now have " + mTwiceBaseCacheSize + " entries"); + if (DEBUG) { + Log.d(TAG, "Storing 2x cache " + Arrays.toString(array) + + " now have " + mTwiceBaseCacheSize + " entries"); + } } } } else if (hashes.length == BASE_SIZE) { @@ -309,8 +312,10 @@ public final class ArrayMap<K, V> implements Map<K, V> { } mBaseCache = array; mBaseCacheSize++; - if (DEBUG) Log.d(TAG, "Storing 1x cache " + array - + " now have " + mBaseCacheSize + " entries"); + if (DEBUG) { + Log.d(TAG, "Storing 1x cache " + Arrays.toString(array) + + " now have " + mBaseCacheSize + " entries"); + } } } } diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java index b5c75b9276b7..73114e26e417 100644 --- a/core/java/android/util/ArraySet.java +++ b/core/java/android/util/ArraySet.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import libcore.util.EmptyArray; import java.lang.reflect.Array; +import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; @@ -194,8 +195,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { array[0] = array[1] = null; sTwiceBaseCacheSize--; if (DEBUG) { - Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have " - + sTwiceBaseCacheSize + " entries"); + Log.d(TAG, "Retrieving 2x cache " + Arrays.toString(mHashes) + + " now have " + sTwiceBaseCacheSize + " entries"); } return; } @@ -221,8 +222,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { array[0] = array[1] = null; sBaseCacheSize--; if (DEBUG) { - Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " - + sBaseCacheSize + " entries"); + Log.d(TAG, "Retrieving 1x cache " + Arrays.toString(mHashes) + + " now have " + sBaseCacheSize + " entries"); } return; } @@ -259,8 +260,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { sTwiceBaseCache = array; sTwiceBaseCacheSize++; if (DEBUG) { - Log.d(TAG, "Storing 2x cache " + array + " now have " + sTwiceBaseCacheSize - + " entries"); + Log.d(TAG, "Storing 2x cache " + Arrays.toString(array) + " now have " + + sTwiceBaseCacheSize + " entries"); } } } @@ -275,7 +276,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> { sBaseCache = array; sBaseCacheSize++; if (DEBUG) { - Log.d(TAG, "Storing 1x cache " + array + " now have " + Log.d(TAG, "Storing 1x cache " + Arrays.toString(array) + " now have " + sBaseCacheSize + " entries"); } } diff --git a/core/java/android/util/proto/ProtoInputStream.java b/core/java/android/util/proto/ProtoInputStream.java index 9789b10a0a61..9a15cd5dbfff 100644 --- a/core/java/android/util/proto/ProtoInputStream.java +++ b/core/java/android/util/proto/ProtoInputStream.java @@ -21,6 +21,8 @@ import android.util.LongArray; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; /** * Class to read to a protobuf stream. @@ -968,26 +970,17 @@ public final class ProtoInputStream extends ProtoStream { public String dumpDebugData() { StringBuilder sb = new StringBuilder(); - sb.append("\nmFieldNumber : 0x" + Integer.toHexString(mFieldNumber)); - sb.append("\nmWireType : 0x" + Integer.toHexString(mWireType)); - sb.append("\nmState : 0x" + Integer.toHexString(mState)); - sb.append("\nmDiscardedBytes : 0x" + Integer.toHexString(mDiscardedBytes)); - sb.append("\nmOffset : 0x" + Integer.toHexString(mOffset)); - sb.append("\nmExpectedObjectTokenStack : "); - if (mExpectedObjectTokenStack == null) { - sb.append("null"); - } else { - sb.append(mExpectedObjectTokenStack); - } - sb.append("\nmDepth : 0x" + Integer.toHexString(mDepth)); - sb.append("\nmBuffer : "); - if (mBuffer == null) { - sb.append("null"); - } else { - sb.append(mBuffer); - } - sb.append("\nmBufferSize : 0x" + Integer.toHexString(mBufferSize)); - sb.append("\nmEnd : 0x" + Integer.toHexString(mEnd)); + sb.append("\nmFieldNumber : 0x").append(Integer.toHexString(mFieldNumber)); + sb.append("\nmWireType : 0x").append(Integer.toHexString(mWireType)); + sb.append("\nmState : 0x").append(Integer.toHexString(mState)); + sb.append("\nmDiscardedBytes : 0x").append(Integer.toHexString(mDiscardedBytes)); + sb.append("\nmOffset : 0x").append(Integer.toHexString(mOffset)); + sb.append("\nmExpectedObjectTokenStack : ") + .append(Objects.toString(mExpectedObjectTokenStack)); + sb.append("\nmDepth : 0x").append(Integer.toHexString(mDepth)); + sb.append("\nmBuffer : ").append(Arrays.toString(mBuffer)); + sb.append("\nmBufferSize : 0x").append(Integer.toHexString(mBufferSize)); + sb.append("\nmEnd : 0x").append(Integer.toHexString(mEnd)); return sb.toString(); } diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java index 8464d2d808d6..58d991370f56 100644 --- a/core/java/android/util/proto/ProtoUtils.java +++ b/core/java/android/util/proto/ProtoUtils.java @@ -20,6 +20,7 @@ import android.util.AggStats; import android.util.Duration; import java.io.IOException; +import java.util.Arrays; /** * This class contains a list of helper functions to write common proto in @@ -91,27 +92,27 @@ public class ProtoUtils { final int wireType = proto.getWireType(); long fieldConstant; - sb.append("Offset : 0x" + Integer.toHexString(proto.getOffset())); - sb.append("\nField Number : 0x" + Integer.toHexString(proto.getFieldNumber())); + sb.append("Offset : 0x").append(Integer.toHexString(proto.getOffset())); + sb.append("\nField Number : 0x").append(Integer.toHexString(proto.getFieldNumber())); sb.append("\nWire Type : "); switch (wireType) { case ProtoStream.WIRE_TYPE_VARINT: - sb.append("varint"); fieldConstant = ProtoStream.makeFieldId(fieldNumber, ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64); - sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant))); + sb.append("varint\nField Value : 0x"); + sb.append(Long.toHexString(proto.readLong(fieldConstant))); break; case ProtoStream.WIRE_TYPE_FIXED64: - sb.append("fixed64"); fieldConstant = ProtoStream.makeFieldId(fieldNumber, ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64); - sb.append("\nField Value : 0x" + Long.toHexString(proto.readLong(fieldConstant))); + sb.append("fixed64\nField Value : 0x"); + sb.append(Long.toHexString(proto.readLong(fieldConstant))); break; case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED: - sb.append("length delimited"); fieldConstant = ProtoStream.makeFieldId(fieldNumber, ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES); - sb.append("\nField Bytes : " + proto.readBytes(fieldConstant)); + sb.append("length delimited\nField Bytes : "); + sb.append(Arrays.toString(proto.readBytes(fieldConstant))); break; case ProtoStream.WIRE_TYPE_START_GROUP: sb.append("start group"); @@ -120,13 +121,13 @@ public class ProtoUtils { sb.append("end group"); break; case ProtoStream.WIRE_TYPE_FIXED32: - sb.append("fixed32"); fieldConstant = ProtoStream.makeFieldId(fieldNumber, ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32); - sb.append("\nField Value : 0x" + Integer.toHexString(proto.readInt(fieldConstant))); + sb.append("fixed32\nField Value : 0x"); + sb.append(Integer.toHexString(proto.readInt(fieldConstant))); break; default: - sb.append("unknown(" + proto.getWireType() + ")"); + sb.append("unknown(").append(proto.getWireType()).append(")"); } return sb.toString(); } diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index 380c1045aea8..9e25a3e18c0c 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -116,8 +116,10 @@ public final class FrameMetrics { * and be issued to the display subsystem. * </p> * <p> - * Equal to the sum of the values of all other time-valued metric - * identifiers. + * The total duration is the difference in time between when the frame + * began and when it ended. This value may not be exactly equal to the + * sum of the values of all other time-valued metric identifiers because + * some stages may happen concurrently. * </p> */ public static final int TOTAL_DURATION = 8; diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 57ba7e9e816f..f9bb880b1881 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -653,7 +653,7 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mInLongPress || mInContextClick) { + if ((mCurrentDownEvent == null) || mInLongPress || mInContextClick) { break; } @@ -736,6 +736,9 @@ public class GestureDetector { break; case MotionEvent.ACTION_UP: + if (mCurrentDownEvent == null) { + break; + } mStillDown = false; MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index e0f02d6e567a..2a45fa563abf 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1943,7 +1943,7 @@ public final class SurfaceControl implements Parcelable { @Override public int hashCode() { - return Objects.hash(supportedDisplayModes, activeDisplayModeId, activeDisplayModeId, + return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId, activeColorMode, hdrCapabilities); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f53dd0c53ef5..e39b7cdfb2f9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9085,6 +9085,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); + + ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); + gestures.add(SelectGesture.class); + gestures.add(DeleteGesture.class); + gestures.add(InsertGesture.class); + outAttrs.setSupportedHandwritingGestures(gestures); return ic; } } diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 884ca77ea377..3250dd8f7308 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -57,6 +57,12 @@ interface ITaskFragmentOrganizerController { * Notifies the server that the organizer has finished handling the given transaction. The * server should apply the given {@link WindowContainerTransaction} for the necessary changes. */ - void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken, - in WindowContainerTransaction wct); + void onTransactionHandled(in IBinder transactionToken, in WindowContainerTransaction wct, + int transitionType, boolean shouldApplyIndependently); + + /** + * Requests the server to apply the given {@link WindowContainerTransaction}. + */ + void applyTransaction(in WindowContainerTransaction wct, int transitionType, + boolean shouldApplyIndependently); } diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 40bffde093d0..3aee472ceeab 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -16,23 +16,32 @@ package android.window; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.WindowConfiguration; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.RemoteAnimationDefinition; +import android.view.WindowManager; import java.util.List; import java.util.concurrent.Executor; @@ -160,21 +169,111 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * {@link #onTransactionReady(TaskFragmentTransaction)} * @param wct {@link WindowContainerTransaction} that the server should apply for * update of the transaction. - * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission - * requirement. + * @param transitionType {@link WindowManager.TransitionType} if it needs to start a + * transition. + * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new + * transition, which will be queued until the sync engine is + * free if there is any other active sync. If {@code false}, + * the {@code wct} will be directly applied to the active sync. + * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission + * for permission enforcement. * @hide */ public void onTransactionHandled(@NonNull IBinder transactionToken, - @NonNull WindowContainerTransaction wct) { + @NonNull WindowContainerTransaction wct, + @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) { wct.setTaskFragmentOrganizer(mInterface); try { - getController().onTransactionHandled(mInterface, transactionToken, wct); + getController().onTransactionHandled(transactionToken, wct, transitionType, + shouldApplyIndependently); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Routes to {@link ITaskFragmentOrganizerController#applyTransaction} instead of + * {@link IWindowOrganizerController#applyTransaction} for the different transition options. + * + * @see #applyTransaction(WindowContainerTransaction, int, boolean, boolean) + */ + @Override + public void applyTransaction(@NonNull WindowContainerTransaction wct) { + // TODO(b/207070762) doing so to keep CTS compatibility. Remove in the next release. + applyTransaction(wct, getTransitionType(wct), false /* shouldApplyIndependently */); + } + + /** + * Requests the server to apply the given {@link WindowContainerTransaction}. + * + * @param wct {@link WindowContainerTransaction} to apply. + * @param transitionType {@link WindowManager.TransitionType} if it needs to start a + * transition. + * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new + * transition, which will be queued until the sync engine is + * free if there is any other active sync. If {@code false}, + * the {@code wct} will be directly applied to the active sync. + * @see com.android.server.wm.WindowOrganizerController#enforceTaskFragmentOrganizerPermission + * for permission enforcement. + * @hide + */ + public void applyTransaction(@NonNull WindowContainerTransaction wct, + @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) { + if (wct.isEmpty()) { + return; + } + wct.setTaskFragmentOrganizer(mInterface); + try { + getController().applyTransaction(wct, transitionType, shouldApplyIndependently); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the default {@link WindowManager.TransitionType} based on the requested + * {@link WindowContainerTransaction}. + * @hide + */ + // TODO(b/207070762): let Extensions to set the transition type instead. + @WindowManager.TransitionType + public static int getTransitionType(@NonNull WindowContainerTransaction wct) { + if (wct.isEmpty()) { + return TRANSIT_NONE; + } + for (WindowContainerTransaction.Change change : wct.getChanges().values()) { + if ((change.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { + // Treat as TRANSIT_CHANGE when there is TaskFragment resizing. + return TRANSIT_CHANGE; + } + } + boolean containsCreatingTaskFragment = false; + boolean containsDeleteTaskFragment = false; + final List<WindowContainerTransaction.HierarchyOp> ops = wct.getHierarchyOps(); + for (int i = ops.size() - 1; i >= 0; i--) { + final int type = ops.get(i).getType(); + if (type == HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT) { + // Treat as TRANSIT_CHANGE when there is activity reparent. + return TRANSIT_CHANGE; + } + if (type == HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT) { + containsCreatingTaskFragment = true; + } else if (type == HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT) { + containsDeleteTaskFragment = true; + } + } + if (containsCreatingTaskFragment) { + return TRANSIT_OPEN; + } + if (containsDeleteTaskFragment) { + return TRANSIT_CLOSE; + } + + // Use TRANSIT_CHANGE as default. + return TRANSIT_CHANGE; + } + + /** * Called when a TaskFragment is created and organized by this organizer. * * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No @@ -309,22 +408,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } // Notify the server, and the server should apply the WindowContainerTransaction. - onTransactionHandled(transaction.getTransactionToken(), wct); - } - - @Override - public void applyTransaction(@NonNull WindowContainerTransaction t) { - t.setTaskFragmentOrganizer(mInterface); - super.applyTransaction(t); - } - - // Suppress the lint because it is not a registration method. - @SuppressWarnings("ExecutorRegistration") - @Override - public int applySyncTransaction(@NonNull WindowContainerTransaction t, - @NonNull WindowContainerTransactionCallback callback) { - t.setTaskFragmentOrganizer(mInterface); - return super.applySyncTransaction(t, callback); + onTransactionHandled(transaction.getTransactionToken(), wct, getTransitionType(wct), + false /* shouldApplyIndependently */); } private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index db58704b5d6a..a99c6bef202f 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -443,6 +443,17 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Finds and removes a task and its children using its container token. The task is removed + * from recents. + * @param containerToken ContainerToken of Task to be removed + */ + @NonNull + public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) { + mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder())); + return this; + } + + /** * Sends a pending intent in sync. * @param sender The PendingIntent sender. * @param intent The fillIn intent to patch over the sender's base intent. @@ -740,10 +751,8 @@ public final class WindowContainerTransaction implements Parcelable { * @hide */ @NonNull - WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) { - if (mTaskFragmentOrganizer != null) { - throw new IllegalStateException("Can't set multiple organizers for one transaction."); - } + public WindowContainerTransaction setTaskFragmentOrganizer( + @NonNull ITaskFragmentOrganizer organizer) { mTaskFragmentOrganizer = organizer; return this; } @@ -1141,6 +1150,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17; public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18; public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19; + public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1270,6 +1280,13 @@ public final class WindowContainerTransaction implements Parcelable { .build(); } + /** create a hierarchy op for deleting a task **/ + public static HierarchyOp createForRemoveTask(@NonNull IBinder container) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK) + .setContainer(container) + .build(); + } + /** Only creates through {@link Builder}. */ private HierarchyOp(int type) { mType = type; @@ -1453,6 +1470,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "{setAlwaysOnTop: container=" + mContainer + " alwaysOnTop=" + mAlwaysOnTop + "}"; + case HIERARCHY_OP_TYPE_REMOVE_TASK: + return "{RemoveTask: task=" + mContainer + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent + " mToTop=" + mToTop diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 33cb56f4ce69..b5fc05bc9938 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -227,6 +227,8 @@ public final class InputMethodDebug { return "HIDE_DOCKED_STACK_ATTACHED"; case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION: return "HIDE_RECENTS_ANIMATION"; + case SoftInputShowHideReason.HIDE_BUBBLES: + return "HIDE_BUBBLES"; case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR: return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; case SoftInputShowHideReason.HIDE_REMOVE_CLIENT: diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 6909965edcd8..7001c69f4042 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -21,6 +21,7 @@ import android.os.BatteryManager; import android.os.BatteryStats.HistoryItem; import android.os.BatteryStats.HistoryStepDetails; import android.os.BatteryStats.HistoryTag; +import android.os.BatteryStats.MeasuredEnergyDetails; import android.os.Parcel; import android.os.ParcelFormatException; import android.os.Process; @@ -113,6 +114,9 @@ public class BatteryStatsHistory { // therefore the tag value is written in the parcel static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000; + static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001; + static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002; + private final Parcel mHistoryBuffer; private final File mSystemDir; private final HistoryStepDetailsCalculator mStepDetailsCalculator; @@ -183,6 +187,7 @@ public class BatteryStatsHistory { private long mTrackRunningHistoryElapsedRealtimeMs = 0; private long mTrackRunningHistoryUptimeMs = 0; private long mHistoryBaseTimeMs; + private boolean mMeasuredEnergyHeaderWritten = false; private byte mLastHistoryStepLevel = 0; @@ -293,6 +298,7 @@ public class BatteryStatsHistory { mLastHistoryElapsedRealtimeMs = 0; mTrackRunningHistoryElapsedRealtimeMs = 0; mTrackRunningHistoryUptimeMs = 0; + mMeasuredEnergyHeaderWritten = false; mHistoryBuffer.setDataSize(0); mHistoryBuffer.setDataPosition(0); @@ -933,6 +939,16 @@ public class BatteryStatsHistory { } /** + * Records measured energy data. + */ + public void recordMeasuredEnergyDetails(long elapsedRealtimeMs, long uptimeMs, + MeasuredEnergyDetails measuredEnergyDetails) { + mHistoryCur.measuredEnergyDetails = measuredEnergyDetails; + mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG; + writeHistoryItem(elapsedRealtimeMs, uptimeMs); + } + + /** * Records a history item with the amount of charge consumed by WiFi. Used on certain devices * equipped with on-device power metering. */ @@ -1219,6 +1235,8 @@ public class BatteryStatsHistory { for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) { entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG); } + mMeasuredEnergyHeaderWritten = false; + // Make a copy of mHistoryCur. HistoryItem copy = new HistoryItem(); copy.setTo(cur); @@ -1256,6 +1274,7 @@ public class BatteryStatsHistory { cur.eventCode = HistoryItem.EVENT_NONE; cur.eventTag = null; cur.tagsFirstOccurrence = false; + cur.measuredEnergyDetails = null; if (DEBUG) { Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos + " now " + mHistoryBuffer.dataPosition() @@ -1348,6 +1367,7 @@ public class BatteryStatsHistory { return; } + int extensionFlags = 0; final long deltaTime = cur.time - last.time; final int lastBatteryLevelInt = buildBatteryLevelInt(last); final int lastStateInt = buildStateInt(last); @@ -1374,6 +1394,17 @@ public class BatteryStatsHistory { if (stateIntChanged) { firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG; } + if (cur.measuredEnergyDetails != null) { + extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG; + if (!mMeasuredEnergyHeaderWritten) { + extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG; + } + } + if (extensionFlags != 0) { + cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG; + } else { + cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG; + } final boolean state2IntChanged = cur.states2 != last.states2; if (state2IntChanged) { firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG; @@ -1491,6 +1522,28 @@ public class BatteryStatsHistory { } dest.writeDouble(cur.modemRailChargeMah); dest.writeDouble(cur.wifiRailChargeMah); + if (extensionFlags != 0) { + dest.writeInt(extensionFlags); + if (cur.measuredEnergyDetails != null) { + if (DEBUG) { + Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.measuredEnergyDetails); + } + if (!mMeasuredEnergyHeaderWritten) { + MeasuredEnergyDetails.EnergyConsumer[] consumers = + cur.measuredEnergyDetails.consumers; + dest.writeInt(consumers.length); + for (MeasuredEnergyDetails.EnergyConsumer consumer : consumers) { + dest.writeInt(consumer.type); + dest.writeInt(consumer.ordinal); + dest.writeString(consumer.name); + } + mMeasuredEnergyHeaderWritten = true; + } + for (long chargeUC : cur.measuredEnergyDetails.chargeUC) { + dest.writeLong(chargeUC); + } + } + } } private int buildBatteryLevelInt(HistoryItem h) { diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 1bf878cb9119..ee3d15b1ad7e 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -33,6 +33,7 @@ public class BatteryStatsHistoryIterator { private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails = new BatteryStats.HistoryStepDetails(); private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>(); + private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails; public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) { mBatteryStatsHistory = history; @@ -198,6 +199,40 @@ public class BatteryStatsHistoryIterator { } cur.modemRailChargeMah = src.readDouble(); cur.wifiRailChargeMah = src.readDouble(); + if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) { + final int extensionFlags = src.readInt(); + if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG) != 0) { + if (mMeasuredEnergyDetails == null) { + mMeasuredEnergyDetails = new BatteryStats.MeasuredEnergyDetails(); + } + + final int consumerCount = src.readInt(); + mMeasuredEnergyDetails.consumers = + new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount]; + mMeasuredEnergyDetails.chargeUC = new long[consumerCount]; + for (int i = 0; i < consumerCount; i++) { + BatteryStats.MeasuredEnergyDetails.EnergyConsumer consumer = + new BatteryStats.MeasuredEnergyDetails.EnergyConsumer(); + consumer.type = src.readInt(); + consumer.ordinal = src.readInt(); + consumer.name = src.readString(); + mMeasuredEnergyDetails.consumers[i] = consumer; + } + } + + if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG) != 0) { + if (mMeasuredEnergyDetails == null) { + throw new IllegalStateException("MeasuredEnergyDetails without a header"); + } + + for (int i = 0; i < mMeasuredEnergyDetails.chargeUC.length; i++) { + mMeasuredEnergyDetails.chargeUC[i] = src.readLong(); + } + cur.measuredEnergyDetails = mMeasuredEnergyDetails; + } + } else { + cur.measuredEnergyDetails = null; + } } private boolean readHistoryTag(Parcel src, int index, BatteryStats.HistoryTag outTag) { diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index 9f3f335f3173..03e7fd1c7403 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -26,7 +26,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.os.storage.StorageManager; import android.text.TextUtils; import com.android.internal.util.ArrayUtils; @@ -347,7 +346,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { @Override public int hashCode() { // Effective Java — Always override hashCode when you override equals - return (17 + mType) * 31 + mCredential.hashCode(); + return Objects.hash(mType, Arrays.hashCode(mCredential)); } @Override diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index be82879c8411..acf4da6a5d07 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -17,28 +17,25 @@ #define LOG_TAG "NativeLibraryHelper" //#define LOG_NDEBUG 0 -#include "core_jni_helpers.h" - -#include <nativehelper/ScopedUtfChars.h> #include <androidfw/ZipFileRO.h> #include <androidfw/ZipUtils.h> -#include <utils/Log.h> -#include <utils/Vector.h> - -#include <zlib.h> - #include <errno.h> #include <fcntl.h> +#include <inttypes.h> +#include <nativehelper/ScopedUtfChars.h> #include <stdlib.h> #include <string.h> -#include <time.h> -#include <unistd.h> -#include <inttypes.h> #include <sys/stat.h> #include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <utils/Log.h> +#include <zlib.h> #include <memory> +#include "core_jni_helpers.h" + #define APK_LIB "lib/" #define APK_LIB_LEN (sizeof(APK_LIB) - 1) @@ -156,7 +153,7 @@ sumFiles(JNIEnv*, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char size_t* total = (size_t*) arg; uint32_t uncompLen; - if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, NULL, NULL)) { + if (!zipFile->getEntryInfo(zipEntry, nullptr, &uncompLen, nullptr, nullptr, nullptr, nullptr)) { return INSTALL_FAILED_INVALID_APK; } @@ -186,7 +183,7 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr uint16_t method; off64_t offset; - if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) { + if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) { ALOGE("Couldn't read zip entry info\n"); return INSTALL_FAILED_INVALID_APK; } @@ -307,24 +304,24 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr class NativeLibrariesIterator { private: NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie) - : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(NULL) { + : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(nullptr) { fileName[0] = '\0'; } public: static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) { - void* cookie = NULL; + void* cookie = nullptr; // Do not specify a suffix to find both .so files and gdbserver. - if (!zipFile->startIteration(&cookie, APK_LIB, NULL /* suffix */)) { - return NULL; + if (!zipFile->startIteration(&cookie, APK_LIB, nullptr /* suffix */)) { + return nullptr; } return new NativeLibrariesIterator(zipFile, debuggable, cookie); } ZipEntryRO next() { - ZipEntryRO next = NULL; - while ((next = mZipFile->nextEntry(mCookie)) != NULL) { + ZipEntryRO next = nullptr; + while ((next = mZipFile->nextEntry(mCookie)) != nullptr) { // Make sure this entry has a filename. if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) { continue; @@ -338,7 +335,7 @@ public: } const char* lastSlash = strrchr(fileName, '/'); - ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName); + ALOG_ASSERT(lastSlash != nullptr, "last slash was null somehow for %s\n", fileName); // Skip directories. if (*(lastSlash + 1) == 0) { @@ -389,24 +386,23 @@ static install_status_t iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, jboolean debuggable, iterFunc callFunc, void* callArg) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); - if (zipFile == NULL) { + if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } std::unique_ptr<NativeLibrariesIterator> it( NativeLibrariesIterator::create(zipFile, debuggable)); - if (it.get() == NULL) { + if (it.get() == nullptr) { return INSTALL_FAILED_INVALID_APK; } const ScopedUtfChars cpuAbi(env, javaCpuAbi); - if (cpuAbi.c_str() == NULL) { - // This would've thrown, so this return code isn't observable by - // Java. + if (cpuAbi.c_str() == nullptr) { + // This would've thrown, so this return code isn't observable by Java. return INSTALL_FAILED_INVALID_APK; } - ZipEntryRO entry = NULL; - while ((entry = it->next()) != NULL) { + ZipEntryRO entry = nullptr; + while ((entry = it->next()) != nullptr) { const char* fileName = it->currentEntry(); const char* lastSlash = it->lastSlash(); @@ -427,31 +423,30 @@ iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, return INSTALL_SUCCEEDED; } - -static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray, - jboolean debuggable) { - const int numAbis = env->GetArrayLength(supportedAbisArray); - Vector<ScopedUtfChars*> supportedAbis; - - for (int i = 0; i < numAbis; ++i) { - supportedAbis.add(new ScopedUtfChars(env, - (jstring) env->GetObjectArrayElement(supportedAbisArray, i))); - } - +static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray, + jboolean debuggable) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); - if (zipFile == NULL) { + if (zipFile == nullptr) { return INSTALL_FAILED_INVALID_APK; } std::unique_ptr<NativeLibrariesIterator> it( NativeLibrariesIterator::create(zipFile, debuggable)); - if (it.get() == NULL) { + if (it.get() == nullptr) { return INSTALL_FAILED_INVALID_APK; } - ZipEntryRO entry = NULL; + const int numAbis = env->GetArrayLength(supportedAbisArray); + + std::vector<ScopedUtfChars> supportedAbis; + supportedAbis.reserve(numAbis); + for (int i = 0; i < numAbis; ++i) { + supportedAbis.emplace_back(env, (jstring)env->GetObjectArrayElement(supportedAbisArray, i)); + } + + ZipEntryRO entry = nullptr; int status = NO_NATIVE_LIBRARIES; - while ((entry = it->next()) != NULL) { + while ((entry = it->next()) != nullptr) { // We're currently in the lib/ directory of the APK, so it does have some native // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the // libraries match. @@ -466,8 +461,8 @@ static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supported const char* abiOffset = fileName + APK_LIB_LEN; const size_t abiSize = lastSlash - abiOffset; for (int i = 0; i < numAbis; i++) { - const ScopedUtfChars* abi = supportedAbis[i]; - if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) { + const ScopedUtfChars& abi = supportedAbis[i]; + if (abi.size() == abiSize && !strncmp(abiOffset, abi.c_str(), abiSize)) { // The entry that comes in first (i.e. with a lower index) has the higher priority. if (((i < status) && (status >= 0)) || (status < 0) ) { status = i; @@ -476,10 +471,6 @@ static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supported } } - for (int i = 0; i < numAbis; ++i) { - delete supportedAbis[i]; - } - return status; } @@ -521,19 +512,19 @@ static jint com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz, jlong apkHandle) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); - void* cookie = NULL; - if (!zipFile->startIteration(&cookie, NULL /* prefix */, RS_BITCODE_SUFFIX)) { + void* cookie = nullptr; + if (!zipFile->startIteration(&cookie, nullptr /* prefix */, RS_BITCODE_SUFFIX)) { return APK_SCAN_ERROR; } char fileName[PATH_MAX]; - ZipEntryRO next = NULL; - while ((next = zipFile->nextEntry(cookie)) != NULL) { + ZipEntryRO next = nullptr; + while ((next = zipFile->nextEntry(cookie)) != nullptr) { if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) { continue; } const char* lastSlash = strrchr(fileName, '/'); - const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1; + const char* baseName = (lastSlash == nullptr) ? fileName : fileName + 1; if (isFilenameSafe(baseName)) { zipFile->endIteration(cookie); return BITCODE_PRESENT; diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp index c5b3d8ae936c..8305bd087bfd 100644 --- a/core/jni/com_android_internal_security_VerityUtils.cpp +++ b/core/jni/com_android_internal_security_VerityUtils.cpp @@ -111,10 +111,10 @@ int measureFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArr ScopedUtfChars path(env, filePath); ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC)); if (rfd.get() < 0) { - return rfd.get(); + return -errno; } - if (auto err = ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data); err < 0) { - return err; + if (::ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) { + return -errno; } if (data->digest_algorithm != FS_VERITY_HASH_ALG_SHA256) { diff --git a/core/proto/android/app/notificationmanager.proto b/core/proto/android/app/notificationmanager.proto index b88315e2dbee..3342c79b7d3f 100644 --- a/core/proto/android/app/notificationmanager.proto +++ b/core/proto/android/app/notificationmanager.proto @@ -45,6 +45,8 @@ message PolicyProto { MEDIA = 7; // System (catch-all for non-never suppressible sounds) are prioritized. SYSTEM = 8; + // Priority conversations are prioritized + CONVERSATIONS = 9; } repeated Category priority_categories = 1; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5b4fc56bc8ba..4d41c3091786 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -923,7 +923,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.SEND_SMS" @@ -937,7 +937,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.RECEIVE_SMS" @@ -951,7 +951,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.READ_SMS" @@ -965,7 +965,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.RECEIVE_WAP_PUSH" @@ -979,7 +979,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.RECEIVE_MMS" @@ -1014,7 +1014,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. @hide Pending API council approval --> @@ -1059,7 +1059,7 @@ targetSdkVersion}</a> is 4 or higher. <p> This is a soft restricted permission which cannot be held by an app it its - full form until the installer on record whitelists the permission. + full form until the installer on record allowlists the permission. Specifically, if the permission is allowlisted the holder app can access external storage and the visual and aural media collections while if the permission is not allowlisted the holder app can only access to the visual @@ -1239,7 +1239,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" @@ -1301,7 +1301,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.READ_CALL_LOG" @@ -1325,7 +1325,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.WRITE_CALL_LOG" @@ -1341,7 +1341,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead @@ -4771,7 +4771,7 @@ android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to allowlist restricted permissions - on any of the whitelists. + on any of the allowlists. @hide --> <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" android:protectionLevel="signature|installer" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e7cae76c60fc..12e8544affa6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4859,6 +4859,14 @@ <item>0.875</item> </string-array> + <!-- When each intermediate SFPS enroll stage ends, as a fraction of total progress. --> + <string-array name="config_sfps_enroll_stage_thresholds" translatable="false"> + <item>0</item> <!-- [-1 // <0/25] No animation 1x --> + <item>0.36</item> <!-- [0 to 8 // <9/25] Pad center 9x --> + <item>0.52</item> <!-- [9 to 12 // <13/25] Tip 4x --> + <item>0.76</item> <!-- [13 to 18 // <19/25] Left 6x --> + </string-array> <!-- [19 to 24 // <25/25] Right 6x --> + <!-- Messages that should not be shown to the user during face auth enrollment. This should be used to hide messages that may be too chatty or messages that the user can't do much about. Entries are defined in android.hardware.biometrics.face@1.0 types.hal --> @@ -5066,6 +5074,10 @@ <!-- If true, the wallpaper will scale regardless of the value of shouldZoomOutWallpaper() --> <bool name="config_alwaysScaleWallpaper">false</bool> + <!-- Set to true to offset the wallpaper when using multiple displays so that it's centered + at the same position as in the largest display.--> + <bool name="config_offsetWallpaperToCenterOfLargestDisplay">false</bool> + <!-- Package name that will receive an explicit manifest broadcast for android.os.action.POWER_SAVE_MODE_CHANGED. --> <string name="config_powerSaveModeChangedListenerPackage" translatable="false"></string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e5b1cf9be748..8488b6865440 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1733,7 +1733,7 @@ <!-- Generic error message shown when the fingerprint operation fails because too many attempts have been made. --> <string name="fingerprint_error_lockout">Too many attempts. Try again later.</string> <!-- Generic error message shown when the fingerprint operation fails because strong authentication is required --> - <string name="fingerprint_error_lockout_permanent">Too many attempts. Fingerprint sensor disabled.</string> + <string name="fingerprint_error_lockout_permanent">Too many attempts. Use screen lock instead.</string> <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint --> <string name="fingerprint_error_unable_to_process">Try again.</string> <!-- Generic error message shown when the user has no enrolled fingerprints --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index aa0bae656da6..b571a53f8551 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2701,7 +2701,7 @@ <java-symbol type="integer" name="config_udfps_illumination_transition_ms" /> <java-symbol type="bool" name="config_is_powerbutton_fps" /> <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" /> - + <java-symbol type="array" name="config_sfps_enroll_stage_thresholds" /> <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" /> <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" /> <java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" /> @@ -4373,6 +4373,10 @@ <!-- The max scale for the wallpaper when it's zoomed in --> <java-symbol type="dimen" name="config_wallpaperMaxScale"/> + <!-- Set to true to offset the wallpaper when using multiple displays so that it's centered + at the same position than in the largest display. --> + <java-symbol type="bool" name="config_offsetWallpaperToCenterOfLargestDisplay" /> + <!-- Set to true to enable the user switcher on the keyguard. --> <java-symbol type="bool" name="config_keyguardUserSwitcher" /> diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java index c5d6f7fad3e8..21a22057d7c8 100644 --- a/core/tests/coretests/src/android/provider/FontsContractTest.java +++ b/core/tests/coretests/src/android/provider/FontsContractTest.java @@ -42,6 +42,7 @@ import androidx.test.filters.SmallTest; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -282,9 +283,10 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { setupPackageManager(); byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); - List<byte[]> certList = Arrays.asList(wrongCert); + List<byte[]> certList = Collections.singletonList(wrongCert); FontRequest requestWrongCerts = new FontRequest( - TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", + Collections.singletonList(certList)); assertNull(FontsContract.getProvider(mPackageManager, requestWrongCerts)); } @@ -293,9 +295,10 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { throws PackageManager.NameNotFoundException { ProviderInfo info = setupPackageManager(); - List<byte[]> certList = Arrays.asList(BYTE_ARRAY); + List<byte[]> certList = Collections.singletonList(BYTE_ARRAY); FontRequest requestRightCerts = new FontRequest( - TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", + Collections.singletonList(certList)); ProviderInfo result = FontsContract.getProvider( mPackageManager, requestRightCerts); @@ -309,7 +312,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY); FontRequest requestRightCerts = new FontRequest( - TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", + Collections.singletonList(certList)); assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts)); } @@ -332,7 +336,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { // {BYTE_ARRAY_2, BYTE_ARRAY_COPY}. List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY); FontRequest requestRightCerts = new FontRequest( - TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", + Collections.singletonList(certList)); assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts)); } @@ -341,9 +346,9 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { ProviderInfo info = setupPackageManager(); List<List<byte[]>> certList = new ArrayList<>(); - byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); - certList.add(Arrays.asList(wrongCert)); - certList.add(Arrays.asList(BYTE_ARRAY)); + certList.add(Collections.singletonList( + Base64.decode("this is a wrong cert", Base64.DEFAULT))); + certList.add(Collections.singletonList(BYTE_ARRAY)); FontRequest requestRightCerts = new FontRequest( TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList); ProviderInfo result = FontsContract.getProvider(mPackageManager, requestRightCerts); @@ -356,7 +361,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { setupPackageManager(); List<List<byte[]>> certList = new ArrayList<>(); - certList.add(Arrays.asList(BYTE_ARRAY)); + certList.add(Collections.singletonList(BYTE_ARRAY)); FontRequest requestRightCerts = new FontRequest( TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList); try { diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 3170177b819c..dfff75b7bf24 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1,12 +1,6 @@ { "version": "1.0.0", "messages": { - "-2146181682": { - "message": "Releasing screen wakelock, obscured by %s", - "level": "DEBUG", - "group": "WM_DEBUG_KEEP_SCREEN_ON", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-2127842445": { "message": "Clearing startingData for token=%s", "level": "VERBOSE", @@ -1783,6 +1777,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-384639879": { + "message": "Acquiring screen wakelock due to %s", + "level": "DEBUG", + "group": "WM_DEBUG_KEEP_SCREEN_ON", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-381522987": { "message": "Display %d state is now (%d), so update recording?", "level": "VERBOSE", @@ -1825,6 +1825,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-353495930": { + "message": "TaskFragmentTransaction changes are not collected in transition because there is an ongoing sync for applySyncTransaction().", + "level": "WARN", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowOrganizerController.java" + }, "-347866078": { "message": "Setting move animation on %s", "level": "VERBOSE", @@ -2389,6 +2395,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "191486492": { + "message": "handleNotObscuredLocked: %s was holding screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by%s", + "level": "DEBUG", + "group": "WM_DEBUG_KEEP_SCREEN_ON", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "200829729": { "message": "ScreenRotationAnimation onAnimationEnd", "level": "DEBUG", @@ -2509,6 +2521,12 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowState.java" }, + "286170861": { + "message": "Creating Pending Transition for TaskFragment: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowOrganizerController.java" + }, "288485303": { "message": "Attempted to set remove mode to a display that does not exist: %d", "level": "WARN", @@ -3055,6 +3073,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "782864973": { + "message": "Releasing screen wakelock, obscured by %s", + "level": "DEBUG", + "group": "WM_DEBUG_KEEP_SCREEN_ON", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "791468751": { "message": "Pausing rotation during re-position", "level": "DEBUG", @@ -4303,18 +4327,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "2088592090": { - "message": "handleNotObscuredLocked: %s was holding screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by%s", - "level": "DEBUG", - "group": "WM_DEBUG_KEEP_SCREEN_ON", - "at": "com\/android\/server\/wm\/RootWindowContainer.java" - }, - "2096635066": { - "message": "Acquiring screen wakelock due to %s", - "level": "DEBUG", - "group": "WM_DEBUG_KEEP_SCREEN_ON", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "2100457473": { "message": "Task=%d contains embedded TaskFragment. Disabled all input during TaskFragment remote animation.", "level": "DEBUG", diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 582488ff8de3..ca3c84729388 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -2796,7 +2796,8 @@ public abstract class ColorSpace { if (mWhitePoint == null || mTransform == null) { throw new IllegalStateException( "ColorSpace (" + this + ") cannot create native object! mWhitePoint: " - + mWhitePoint + " mTransform: " + mTransform); + + Arrays.toString(mWhitePoint) + " mTransform: " + + Arrays.toString(mTransform)); } // This mimics the old code that was in native. diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt deleted file mode 100644 index c7062e093135..000000000000 --- a/ktfmt_includes.txt +++ /dev/null @@ -1,17 +0,0 @@ -packages/SystemUI/compose/ -packages/SystemUI/screenshot/ -packages/SystemUI/src/com/android/systemui/people/data -packages/SystemUI/src/com/android/systemui/people/ui -packages/SystemUI/src/com/android/systemui/keyguard/data -packages/SystemUI/src/com/android/systemui/keyguard/dagger -packages/SystemUI/src/com/android/systemui/keyguard/domain -packages/SystemUI/src/com/android/systemui/keyguard/shared -packages/SystemUI/src/com/android/systemui/keyguard/ui -packages/SystemUI/src/com/android/systemui/qs/footer -packages/SystemUI/src/com/android/systemui/security -packages/SystemUI/src/com/android/systemui/common/ -packages/SystemUI/tests/utils/src/com/android/systemui/qs/ -packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt -packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt -packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt -packages/SystemUI/tests/src/com/android/systemui/qs/footer/
\ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 02af9160301c..5727b91376d9 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; +import static android.window.TaskFragmentOrganizer.getTransitionType; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -109,7 +110,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; - private final Object mLock = new Object(); + final Object mLock = new Object(); private final ActivityStartMonitor mActivityStartMonitor; public SplitController() { @@ -209,8 +210,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - // Notify the server, and the server should apply the WindowContainerTransaction. - mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct); + // Notify the server, and the server should apply and merge the + // WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction. + mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct, + getTransitionType(wct), false /* shouldApplyIndependently */); updateCallbackIfNecessary(); } } @@ -221,6 +224,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is created. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, @@ -245,6 +251,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. * @param taskFragmentInfo Info of the TaskFragment that is changed. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, @@ -430,6 +439,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * transaction operation. * @param exception exception from the server side. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentError(@NonNull WindowContainerTransaction wct, @@ -792,6 +804,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Checks if there is a rule to split the two activities. If there is one, puts them into split * and returns {@code true}. Otherwise, returns {@code false}. */ + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") @GuardedBy("mLock") private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -866,23 +881,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Called when we have been waiting too long for the TaskFragment to become non-empty after * creation. */ + @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { - synchronized (mLock) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - onTaskFragmentAppearEmptyTimeout(wct, container); - mPresenter.applyTransaction(wct); - } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + onTaskFragmentAppearEmptyTimeout(wct, container); + // Can be applied independently as a timeout callback. + mPresenter.applyTransaction(wct, getTransitionType(wct), + true /* shouldApplyIndependently */); } /** * Called when we have been waiting too long for the TaskFragment to become non-empty after * creation. */ + @GuardedBy("mLock") void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { - synchronized (mLock) { - mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); - } + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } /** @@ -1711,7 +1726,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final WindowContainerTransaction wct = new WindowContainerTransaction(); SplitController.this.onActivityCreated(wct, activity); - mPresenter.applyTransaction(wct); + // The WCT should be applied and merged to the activity launch transition. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); } } @@ -1720,7 +1737,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final WindowContainerTransaction wct = new WindowContainerTransaction(); SplitController.this.onActivityConfigurationChanged(wct, activity); - mPresenter.applyTransaction(wct); + // The WCT should be applied and merged to the Task change transition so that the + // placeholder is launched in the same transition. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); } } @@ -1772,7 +1792,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, launchingActivity); if (launchedInTaskFragment != null) { - mPresenter.applyTransaction(wct); + // Make sure the WCT is applied immediately instead of being queued so that the + // TaskFragment will be ready before activity attachment. + mPresenter.applyTransaction(wct, getTransitionType(wct), + false /* shouldApplyIndependently */); // Amend the request to let the WM know that the activity should be placed in // the dedicated container. options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 2b069d72e46f..2ef8e4c64855 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -38,6 +38,7 @@ import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -171,6 +172,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * created and the activity will be re-parented to it. * @param rule The split rule to be applied to the container. */ + @GuardedBy("mController.mLock") void createNewSplitContainer(@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { @@ -187,8 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; - if (rule.shouldClearTop() && curSecondaryContainer != null) { - // Do not reuse the current TaskFragment if the rule is to clear top. + if (curSecondaryContainer != null + && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) { + // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below + // the primary TaskFragment. containerToAvoid = curSecondaryContainer; } final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 77e26c07f304..45645b2018c3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -162,4 +162,8 @@ class TaskContainer { } return null; } + + int indexOf(@NonNull TaskFragmentContainer child) { + return mContainers.indexOf(child); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 11c0db320646..2843b145cb3e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -28,6 +28,7 @@ import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -251,6 +252,7 @@ class TaskFragmentContainer { return mInfo; } + @GuardedBy("mController.mLock") void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) { if (!mIsFinished && mInfo == null && info.isEmpty()) { // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no @@ -258,10 +260,12 @@ class TaskFragmentContainer { // it is still empty after timeout. if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { mAppearEmptyTimeout = () -> { - mAppearEmptyTimeout = null; - // Call without the pass-in wct when timeout. We need to applyWct directly - // in this case. - mController.onTaskFragmentAppearEmptyTimeout(this); + synchronized (mController.mLock) { + mAppearEmptyTimeout = null; + // Call without the pass-in wct when timeout. We need to applyWct directly + // in this case. + mController.onTaskFragmentAppearEmptyTimeout(this); + } }; mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); } else { @@ -501,6 +505,18 @@ class TaskFragmentContainer { return new Size(maxMinWidth, maxMinHeight); } + /** Whether the current TaskFragment is above the {@code other} TaskFragment. */ + boolean isAbove(@NonNull TaskFragmentContainer other) { + if (mTaskContainer != other.mTaskContainer) { + throw new IllegalArgumentException( + "Trying to compare two TaskFragments in different Task."); + } + if (this == other) { + throw new IllegalArgumentException("Trying to compare a TaskFragment with itself."); + } + return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other); + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index d0eaf34274aa..58a627bafa16 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -33,7 +32,6 @@ import static org.mockito.Mockito.never; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Point; -import android.os.Handler; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; import android.window.TaskFragmentTransaction; @@ -67,10 +65,7 @@ public class JetpackTaskFragmentOrganizerTest { private WindowContainerTransaction mTransaction; @Mock private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback; - @Mock private SplitController mSplitController; - @Mock - private Handler mHandler; private JetpackTaskFragmentOrganizer mOrganizer; @Before @@ -78,8 +73,9 @@ public class JetpackTaskFragmentOrganizerTest { MockitoAnnotations.initMocks(this); mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback); mOrganizer.registerOrganizer(); + mSplitController = new SplitController(); spyOn(mOrganizer); - doReturn(mHandler).when(mSplitController).getHandler(); + spyOn(mSplitController); } @Test diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index f7436108d3e3..4dbbc04f9444 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -127,7 +127,7 @@ public class SplitControllerTest { mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); spyOn(mSplitPresenter); - doNothing().when(mSplitPresenter).applyTransaction(any()); + doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); final Configuration activityConfig = new Configuration(); activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); @@ -1000,7 +1000,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentAppeared(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1014,7 +1015,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1028,7 +1030,8 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onTaskFragmentVanished(any(), eq(info)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1043,7 +1046,8 @@ public class SplitControllerTest { verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID), eq(taskConfig)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1062,7 +1066,8 @@ public class SplitControllerTest { verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType), eq(exception)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } @Test @@ -1078,7 +1083,8 @@ public class SplitControllerTest { verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent), eq(activityToken)); - verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any()); + verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); } /** Creates a mock activity in the organizer process. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index da724d9d9311..25f0e25eec75 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -39,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -247,6 +248,26 @@ public class SplitPresenterTest { verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); } + @Test + public void testCreateNewSplitContainer_secondaryAbovePrimary() { + final Activity secondaryActivity = createMockActivity(); + final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID); + final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); + final SplitPairRule rule = new SplitPairRule.Builder(pair -> + pair.first == mActivity && pair.second == secondaryActivity, pair -> false, + metrics -> true) + .setShouldClearTop(false) + .build(); + + mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule); + + assertEquals(primaryTf, mController.getContainerWithActivity(mActivity)); + final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity( + secondaryActivity); + assertNotEquals(bottomTf, secondaryTf); + assertTrue(secondaryTf.isAbove(primaryTf)); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 6cbecff81be5..1bc81ee3dcc7 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -288,6 +288,18 @@ public class TaskFragmentContainerTest { } @Test + public void testIsAbove() { + final TaskContainer taskContainer = new TaskContainer(TASK_ID); + final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, + mIntent, taskContainer, mController); + + assertTrue(container1.isAbove(container0)); + assertFalse(container0.isAbove(container1)); + } + + @Test public void testGetBottomMostActivity() { final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml index 6fcd1de892a3..ddfb5c27e701 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_letterboxed_app.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_light_bulb.xml @@ -15,15 +15,12 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_title_icon_width" + android:height="@dimen/letterbox_education_dialog_title_icon_height" + android:viewportWidth="45" + android:viewportHeight="44"> + <path android:fillColor="@color/letterbox_education_accent_primary" - android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> - <path - android:fillColor="@color/letterbox_education_accent_primary" - android:pathData="M 17 14 L 31 14 Q 32 14 32 15 L 32 33 Q 32 34 31 34 L 17 34 Q 16 34 16 33 L 16 15 Q 16 14 17 14 Z" /> + android:pathData="M11 40H19C19 42.2 17.2 44 15 44C12.8 44 11 42.2 11 40ZM7 38L23 38V34L7 34L7 38ZM30 19C30 26.64 24.68 30.72 22.46 32L7.54 32C5.32 30.72 0 26.64 0 19C0 10.72 6.72 4 15 4C23.28 4 30 10.72 30 19ZM26 19C26 12.94 21.06 8 15 8C8.94 8 4 12.94 4 19C4 23.94 6.98 26.78 8.7 28L21.3 28C23.02 26.78 26 23.94 26 19ZM39.74 14.74L37 16L39.74 17.26L41 20L42.26 17.26L45 16L42.26 14.74L41 12L39.74 14.74ZM35 12L36.88 7.88L41 6L36.88 4.12L35 0L33.12 4.12L29 6L33.12 7.88L35 12Z" /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml index cbfcfd06e3b7..22a8f39ca687 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml @@ -15,18 +15,16 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_icon_width" + android:height="@dimen/letterbox_education_dialog_icon_height" + android:viewportWidth="40" + android:viewportHeight="32"> + <path android:fillColor="@color/letterbox_education_text_secondary" android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> + android:pathData="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H36C38.2091 32 40 30.2091 40 28V4C40 1.79086 38.2091 0 36 0H4ZM36 4H4V28H36V4Z" /> <path android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M26 16L34 24L26 32V16Z" /> + android:pathData="M19.98 8L17.16 10.82L20.32 14L12 14V18H20.32L17.14 21.18L19.98 24L28 16.02L19.98 8Z" /> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml deleted file mode 100644 index 469eb1e14849..000000000000 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:fillType="evenOdd" - android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" /> -</vector> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml index dcb8aed05c9c..15e65f716b20 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml @@ -15,18 +15,12 @@ ~ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/letterbox_education_dialog_icon_size" - android:height="@dimen/letterbox_education_dialog_icon_size" - android:viewportWidth="48" - android:viewportHeight="48"> + android:width="@dimen/letterbox_education_dialog_icon_width" + android:height="@dimen/letterbox_education_dialog_icon_height" + android:viewportWidth="40" + android:viewportHeight="32"> + <path android:fillColor="@color/letterbox_education_text_secondary" - android:fillType="evenOdd" - android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" /> - <path - android:fillColor="@color/letterbox_education_text_secondary" - android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" /> + android:pathData="M40 28L40 4C40 1.8 38.2 -7.86805e-08 36 -1.74846e-07L26 -6.11959e-07C23.8 -7.08124e-07 22 1.8 22 4L22 28C22 30.2 23.8 32 26 32L36 32C38.2 32 40 30.2 40 28ZM14 28L4 28L4 4L14 4L14 28ZM18 28L18 4C18 1.8 16.2 -1.04033e-06 14 -1.1365e-06L4 -1.57361e-06C1.8 -1.66978e-06 -7.86805e-08 1.8 -1.74846e-07 4L-1.22392e-06 28C-1.32008e-06 30.2 1.8 32 4 32L14 32C16.2 32 18 30.2 18 28Z" /> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml index cd1d99ae58b0..c65f24d84e37 100644 --- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml +++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml @@ -26,7 +26,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginBottom="12dp"/> + android:layout_marginBottom="20dp"/> <TextView android:id="@+id/letterbox_education_dialog_action_text" diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml index 95923763d889..3a44eb9089dd 100644 --- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml +++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml @@ -50,13 +50,16 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" - android:padding="24dp"> + android:paddingTop="32dp" + android:paddingBottom="32dp" + android:paddingLeft="56dp" + android:paddingRight="56dp"> <ImageView - android:layout_width="@dimen/letterbox_education_dialog_icon_size" - android:layout_height="@dimen/letterbox_education_dialog_icon_size" - android:layout_marginBottom="12dp" - android:src="@drawable/letterbox_education_ic_letterboxed_app"/> + android:layout_width="@dimen/letterbox_education_dialog_title_icon_width" + android:layout_height="@dimen/letterbox_education_dialog_title_icon_height" + android:layout_marginBottom="17dp" + android:src="@drawable/letterbox_education_ic_light_bulb"/> <TextView android:id="@+id/letterbox_education_dialog_title" @@ -68,16 +71,6 @@ android:textColor="@color/compat_controls_text" android:textSize="24sp"/> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:lineSpacingExtra="4sp" - android:text="@string/letterbox_education_dialog_subtext" - android:textAlignment="center" - android:textColor="@color/letterbox_education_text_secondary" - android:textSize="14sp"/> - <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -88,16 +81,16 @@ <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - app:icon="@drawable/letterbox_education_ic_screen_rotation" - app:text="@string/letterbox_education_screen_rotation_text"/> + app:icon="@drawable/letterbox_education_ic_reposition" + app:text="@string/letterbox_education_reposition_text"/> <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart= "@dimen/letterbox_education_dialog_space_between_actions" - app:icon="@drawable/letterbox_education_ic_reposition" - app:text="@string/letterbox_education_reposition_text"/> + app:icon="@drawable/letterbox_education_ic_split_screen" + app:text="@string/letterbox_education_split_screen_text"/> </LinearLayout> @@ -105,7 +98,7 @@ android:id="@+id/letterbox_education_dialog_dismiss_button" android:layout_width="match_parent" android:layout_height="56dp" - android:layout_marginTop="48dp" + android:layout_marginTop="40dp" android:background= "@drawable/letterbox_education_dismiss_button_background_ripple" android:text="@string/letterbox_education_got_it" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 1dac9caba01e..d2dd8d6b56a9 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -246,8 +246,17 @@ <!-- The corner radius of the letterbox education dialog. --> <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen> - <!-- The size of an icon in the letterbox education dialog. --> - <dimen name="letterbox_education_dialog_icon_size">48dp</dimen> + <!-- The width of the top icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_title_icon_width">45dp</dimen> + + <!-- The height of the top icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_title_icon_height">44dp</dimen> + + <!-- The width of an icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_icon_width">40dp</dimen> + + <!-- The height of an icon in the letterbox education dialog. --> + <dimen name="letterbox_education_dialog_icon_height">32dp</dimen> <!-- The fixed width of the dialog if there is enough space in the parent. --> <dimen name="letterbox_education_dialog_width">472dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 679bfb9d5187..b48a508fddb8 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -177,16 +177,13 @@ <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string> <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_dialog_title">Some apps work best in portrait</string> + <string name="letterbox_education_dialog_title">See and do more</string> - <!-- The subtext of the letterbox education dialog. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_dialog_subtext">Try one of these options to make the most of your space</string> - - <!-- Description of the rotate screen action. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_screen_rotation_text">Rotate your device to go full screen</string> + <!-- Description of the split screen action. [CHAR LIMIT=NONE] --> + <string name="letterbox_education_split_screen_text">Drag in another app for split-screen</string> <!-- Description of the reposition app action. [CHAR LIMIT=NONE] --> - <string name="letterbox_education_reposition_text">Double-tap next to an app to reposition it</string> + <string name="letterbox_education_reposition_text">Double-tap outside an app to reposition it</string> <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] --> <string name="letterbox_education_got_it">Got it</string> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b7959eb629c1..4c85d2090ec5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -80,7 +80,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public static final int PARALLAX_DISMISSING = 1; public static final int PARALLAX_ALIGN_CENTER = 2; - private static final int FLING_ANIMATION_DURATION = 250; + private static final int FLING_RESIZE_DURATION = 250; + private static final int FLING_SWITCH_DURATION = 350; + private static final int FLING_ENTER_DURATION = 350; + private static final int FLING_EXIT_DURATION = 350; private final int mDividerWindowWidth; private final int mDividerInsets; @@ -93,6 +96,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final Rect mBounds1 = new Rect(); // Bounds2 final position should be always at bottom or right private final Rect mBounds2 = new Rect(); + // The temp bounds outside of display bounds for side stage when split screen inactive to avoid + // flicker next time active split screen. + private final Rect mInvisibleBounds = new Rect(); private final Rect mWinBounds1 = new Rect(); private final Rect mWinBounds2 = new Rect(); private final SplitLayoutHandler mSplitLayoutHandler; @@ -141,6 +147,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange resetDividerPosition(); mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide); + + updateInvisibleRect(); } private int getDividerInsets(Resources resources, Display display) { @@ -239,6 +247,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange rect.offset(-mRootBounds.left, -mRootBounds.top); } + /** Gets bounds size equal to root bounds but outside of screen, used for position side stage + * when split inactive to avoid flicker when next time active. */ + public void getInvisibleBounds(Rect rect) { + rect.set(mInvisibleBounds); + } + /** Returns leash of the current divider bar. */ @Nullable public SurfaceControl getDividerLeash() { @@ -258,6 +272,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom)); } + private void updateInvisibleRect() { + mInvisibleBounds.set(mRootBounds.left, mRootBounds.top, + isLandscape() ? mRootBounds.right / 2 : mRootBounds.right, + isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2); + mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0, + isLandscape() ? 0 : mRootBounds.bottom); + } + /** Applies new configuration, returns {@code false} if there's no effect to the layout. */ public boolean updateConfiguration(Configuration configuration) { // Update the split bounds when necessary. Besides root bounds changed, split bounds need to @@ -283,6 +305,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = rotation; mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); initDividerPosition(mTempRect); + updateInvisibleRect(); return true; } @@ -405,6 +428,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mFreezeDividerWindow = freezeDividerWindow; } + /** Update current layout as divider put on start or end position. */ + public void setDividerAtBorder(boolean start) { + final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position + : mDividerSnapAlgorithm.getDismissEndTarget().position; + setDividePosition(pos, false /* applyLayoutChange */); + } + /** * Updates bounds with the passing position. Usually used to update recording bounds while * performing animation or dragging divider bar to resize the splits. @@ -449,17 +479,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.flag) { case FLAG_DISMISS_START: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; case FLAG_DISMISS_END: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; default: - flingDividePosition(currentPosition, snapTarget.position, + flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); break; } @@ -514,12 +544,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void flingDividerToDismiss(boolean toEnd, int reason) { final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position : mDividerSnapAlgorithm.getDismissStartTarget().position; - flingDividePosition(getDividePosition(), target, + flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); } + /** Fling divider from current position to center position. */ + public void flingDividerToCenter() { + final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; + flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION, + () -> setDividePosition(pos, true /* applyLayoutChange */)); + } + @VisibleForTesting - void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) { + void flingDividePosition(int from, int to, int duration, + @Nullable Runnable flingFinishedCallback) { if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); @@ -529,7 +567,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } ValueAnimator animator = ValueAnimator .ofInt(from, to) - .setDuration(FLING_ANIMATION_DURATION); + .setDuration(duration); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.addUpdateListener( animation -> updateDivideBounds((int) animation.getAnimatedValue())); @@ -586,7 +624,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); - set.setDuration(FLING_ANIMATION_DURATION); + set.setDuration(FLING_SWITCH_DURATION); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index dd50fa0817c2..fd4c85fad77f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -92,6 +92,12 @@ public class FreeformTaskTransitionHandler @Override + public void startRemoveTransition(WindowContainerTransaction wct) { + final int type = WindowManager.TRANSIT_CLOSE; + mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this)); + } + + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index c947cf1b8cd1..8da4c6ab4b36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -40,4 +40,12 @@ public interface FreeformTaskTransitionStarter { * */ void startMinimizedModeTransition(WindowContainerTransaction wct); + + /** + * Starts close window transition + * + * @param wct the {@link WindowContainerTransaction} that closes the task + * + */ + void startRemoveTransition(WindowContainerTransaction wct); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index f1465f421c5b..e9f9bb5d7327 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -287,6 +287,9 @@ public class FullscreenTaskListener<T extends AutoCloseable> } private void releaseWindowDecor(T windowDecor) { + if (windowDecor == null) { + return; + } try { windowDecor.close(); } catch (Exception e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7e83d2fa0a0b..c08aa5a6f208 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -488,13 +489,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); - // If split still not active, apply windows bounds first to avoid surface reset to - // wrong pos by SurfaceAnimator from wms. - // TODO(b/223325631): check is it still necessary after improve enter transition done. - if (!mMainStage.isActive()) { - updateWindowBounds(mSplitLayout, wct); - } - wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -519,6 +513,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(splitRatio); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); @@ -628,6 +623,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); @@ -640,8 +636,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else { wct.startTask(sideTaskId, sideOptions); } - // Using legacy transitions, so we can't use blast sync since it conflicts. - mTaskOrganizer.applyTransaction(wct); + + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(true, t); updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); @@ -893,10 +889,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsDividerRemoteAnimating = false; + mSplitLayout.getInvisibleBounds(mTempRect1); if (childrenToTop == null) { mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); + wct.setForceTranslucent(mRootTaskInfo.token, true); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); onTransitionAnimationComplete(); } else { // Expand to top side split as full screen for fading out decor animation and dismiss @@ -907,27 +906,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage; tempFullStage.resetBounds(wct); wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, - mRootTaskInfo.configuration.smallestScreenWidthDp); + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); dismissStage.dismiss(wct, false /* toTop */); } mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mSideStage.mRootLeash, null); - t.setPosition(mMainStage.mRootLeash, 0, 0) - .setPosition(mSideStage.mRootLeash, 0, 0); t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer); setDividerVisibility(false, t); - // In this case, exit still under progress, fade out the split decor after first WCT - // done and do remaining WCT after animation finished. - if (childrenToTop != null) { + if (childrenToTop == null) { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + } else { + // In this case, exit still under progress, fade out the split decor after first WCT + // done and do remaining WCT after animation finished. childrenToTop.fadeOutDecor(() -> { WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); mIsExiting = false; childrenToTop.dismiss(finishedWCT, true /* toTop */); finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */); - mTaskOrganizer.applyTransaction(finishedWCT); + finishedWCT.setForceTranslucent(mRootTaskInfo.token, true); + finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(finishedWCT); + mSyncQueue.runInSync(at -> { + at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right); + }); onTransitionAnimationComplete(); }); } @@ -996,6 +1000,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(wct, true /* includingTopTask */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); } void finishEnterSplitScreen(SurfaceControl.Transaction t) { @@ -1221,7 +1226,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - mTaskOrganizer.applyTransaction(wct); + wct.setForceTranslucent(mRootTaskInfo.token, true); + mSplitLayout.getInvisibleBounds(mTempRect1); + wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); + }); } private void onRootTaskVanished() { @@ -1377,10 +1388,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // TODO (b/238697912) : Add the validation to prevent entering non-recovered status final WindowContainerTransaction wct = new WindowContainerTransaction(); mSplitLayout.init(); - prepareEnterSplitScreen(wct); + mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT); + mMainStage.activate(wct, true /* includingTopTask */); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + mSyncQueue.runInSync(t -> { + mSplitLayout.flingDividerToCenter(); + }); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; @@ -1822,6 +1838,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // properly for the animation itself. mSplitLayout.release(); mSplitLayout.resetDividerPosition(); + mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index ad539568e3eb..83aa539d24d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -148,7 +148,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption public void onClick(View v) { final int id = v.getId(); if (id == R.id.close_window) { - mActivityTaskManager.removeTask(mTaskId); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.removeTask(mTaskToken); + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mTransitionStarter.startRemoveTransition(wct); + } else { + mSyncQueue.queue(wct); + } } else if (id == R.id.maximize_window) { WindowContainerTransaction wct = new WindowContainerTransaction(); RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index cb74315732ab..cc987dc7986c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -151,15 +151,15 @@ fun FlickerTestParameter.splitAppLayerBoundsChanges( assertLayers { if (landscapePosLeft) { this.splitAppLayerBoundsSnapToDivider( - component, landscapePosLeft, portraitPosTop, endRotation) + component, landscapePosLeft, portraitPosTop, endRotation) + } else { + this.splitAppLayerBoundsSnapToDivider( + component, landscapePosLeft, portraitPosTop, endRotation) .then() .isInvisible(component) .then() .splitAppLayerBoundsSnapToDivider( component, landscapePosLeft, portraitPosTop, endRotation) - } else { - this.splitAppLayerBoundsSnapToDivider( - component, landscapePosLeft, portraitPosTop, endRotation) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt index 6695c17ed514..1b8a44bbe78e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt @@ -27,7 +27,6 @@ class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper( ) { companion object { - const val TEST_REPETITIONS = 1 const val TIMEOUT_MS = 3_000L } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index e7f9d9a9d73d..52e5d7e14ab9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -42,7 +42,6 @@ class SplitScreenHelper( ) : BaseAppHelper(instrumentation, activityLabel, componentInfo) { companion object { - const val TEST_REPETITIONS = 1 const val TIMEOUT_MS = 3_000L const val DRAG_DURATION_MS = 1_000L const val NOTIFICATION_SCROLLER = "notification_stack_scroller" @@ -98,13 +97,29 @@ class SplitScreenHelper( secondaryApp: IComponentMatcher, ) { wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withWindowSurfaceAppeared(primaryApp) .withWindowSurfaceAppeared(secondaryApp) .withSplitDividerVisible() .waitForAndVerify() } + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + primaryApp: SplitScreenHelper, + secondaryApp: SplitScreenHelper + ) { + tapl.workspace.switchToOverview().dismissAllTasks() + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder() + .withHomeActivityVisible() + .waitForAndVerify() + splitFromOverview(tapl) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + fun splitFromOverview(tapl: LauncherInstrumentation) { // Note: The initial split position in landscape is different between tablet and phone. // In landscape, tablet will let the first app split to right side, and phone will @@ -268,24 +283,35 @@ class SplitScreenHelper( ?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width * 2 / 3, displayBounds.height * 2 / 3)) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3)) wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) .waitForAndVerify() } fun dragDividerToDismissSplit( device: UiDevice, - wmHelper: WindowManagerStateHelper + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean ) { val displayBounds = wmHelper.currentState.layerState .displays.firstOrNull { !it.isVirtual } ?.layerStackSpace ?: error("Display not found") val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) - dividerBar.drag(Point(displayBounds.width * 4 / 5, displayBounds.height * 4 / 5)) + dividerBar.drag(Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + })) } fun doubleTapDividerToSwitch(device: UiDevice) { @@ -297,7 +323,7 @@ class SplitScreenHelper( dividerBar.click() } - fun copyContentFromLeftToRight( + fun copyContentInSplit( instrumentation: Instrumentation, device: UiDevice, sourceApp: IComponentNameMatcher, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index 6cbb685850fa..102a78ba16ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -30,8 +30,6 @@ import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -49,30 +47,18 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - protected val textEditApp = SplitScreenHelper.getIme(instrumentation) - - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } + private val textEditApp = SplitScreenHelper.getIme(instrumentation) override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - textEditApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(primaryApp.appName) - .dragToSplitscreen(primaryApp.`package`, textEditApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, textEditApp, primaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp) } } transitions { - SplitScreenHelper.copyContentFromLeftToRight( + SplitScreenHelper.copyContentInSplit( instrumentation, device, primaryApp, textEditApp) } } @@ -92,12 +78,12 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS @Presubmit @Test fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible( - primaryApp, landscapePosLeft = true, portraitPosTop = true) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible( - textEditApp, landscapePosLeft = false, portraitPosTop = false) + textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index 581826ef889f..bf91292811e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -26,6 +26,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -33,14 +34,11 @@ import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized - /** * Test dismiss split screen by dragging the divider bar. * @@ -53,31 +51,23 @@ import org.junit.runners.Parameterized @Group1 class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - tapl.goHome() - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } } transitions { - SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper) + if (tapl.isTablet) { + SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper, + dragToRight = false, dragToBottom = true) + } else { + SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper, + dragToRight = true, dragToBottom = true) + } wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withFullScreenApp(secondaryApp) .waitForAndVerify() } @@ -98,17 +88,23 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen @Presubmit @Test fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsFullscreenAtEnd() { testSpec.assertLayers { this.isVisible(secondaryApp) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) .then() .isInvisible(secondaryApp) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isVisible(secondaryApp, isOptional = true) + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT, isOptional = true) + .then() + .contains(SPLIT_SCREEN_DIVIDER_COMPONENT) .then() - .isVisible(secondaryApp) .invoke("secondaryAppBoundsIsFullscreenAtEnd") { val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation) it.visibleRegion(secondaryApp).coversExactly(displayBounds) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index 5c051e859d59..20a7423be681 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -30,8 +30,6 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesInvisible import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -52,30 +50,17 @@ class DismissSplitScreenByGoHome( testSpec: FlickerTestParameter ) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } } transitions { tapl.goHome() wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withHomeActivityVisible() .waitForAndVerify() } @@ -96,12 +81,12 @@ class DismissSplitScreenByGoHome( @Presubmit @Test fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( - secondaryApp, landscapePosLeft = true, portraitPosTop = true) + secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 9ca9ab01fd7b..8f7673b77429 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -31,8 +31,6 @@ import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsChanges -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -51,25 +49,12 @@ import org.junit.runners.Parameterized @Group1 class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - tapl.goHome() - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } } transitions { @@ -108,12 +93,12 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes @Presubmit @Test fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = true, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges( - secondaryApp, landscapePosLeft = true, portraitPosTop = true) + secondaryApp, landscapePosLeft = false, portraitPosTop = true) /** {@inheritDoc} */ @Postsubmit @@ -189,7 +174,7 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes supportedRotations = listOf(Surface.ROTATION_0), // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = - listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)) + listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt index 8cb5d7c24ced..7ce23c5f9a4e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -170,8 +170,7 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS) + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 525e09a19d2f..58f7b048bc70 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -31,8 +31,6 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.layerKeepVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -51,24 +49,12 @@ import org.junit.runners.Parameterized @Group1 class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) } } transitions { @@ -94,12 +80,12 @@ class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreen @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, landscapePosLeft = true, portraitPosTop = true) + primaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, landscapePosLeft = false, portraitPosTop = false) + secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index c030603a6b55..0dd6706fb00e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -30,8 +30,6 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -51,28 +49,15 @@ import org.junit.runners.Parameterized class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation) - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) thirdApp.launchViaIntent(wmHelper) wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withWindowSurfaceAppeared(thirdApp) .waitForAndVerify() } @@ -98,12 +83,12 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, landscapePosLeft = true, portraitPosTop = true) + secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index b8565f3e89b2..dc8ba0caa4e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -30,8 +30,6 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -50,28 +48,15 @@ import org.junit.runners.Parameterized @Group1 class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withHomeActivityVisible() .waitForAndVerify() } @@ -97,12 +82,12 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, landscapePosLeft = true, portraitPosTop = true) + secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index 20d7f2cf57e8..e5924c563e5b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -30,8 +30,6 @@ import com.android.wm.shell.flicker.helpers.SplitScreenHelper import com.android.wm.shell.flicker.layerBecomesVisible import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -50,28 +48,15 @@ import org.junit.runners.Parameterized @Group1 class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { - // TODO(b/231399940): Remove this once we can use recent shortcut to enter split. - @Before - open fun before() { - Assume.assumeTrue(tapl.isTablet) - } - override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { eachRun { - primaryApp.launchViaIntent(wmHelper) - // TODO(b/231399940): Use recent shortcut to enter split. - tapl.launchedAppState.taskbar - .openAllApps() - .getAppIcon(secondaryApp.appName) - .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) - SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder() - .withAppTransitionIdle() .withHomeActivityVisible() .waitForAndVerify() } @@ -99,12 +84,12 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, landscapePosLeft = false, portraitPosTop = false) + primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, landscapePosLeft = true, portraitPosTop = true) + secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 95725bbfd855..695550dd8fa5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -159,7 +159,8 @@ public class SplitLayoutTests extends ShellTestCase { } private void waitDividerFlingFinished() { - verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture()); + verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(), + mRunnableCaptor.capture()); mRunnableCaptor.getValue().run(); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 88f73d6cf251..b11e542472da 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -93,6 +93,10 @@ cc_defaults { cc_defaults { name: "hwui_static_deps", + defaults: [ + "android.hardware.graphics.common-ndk_shared", + "android.hardware.graphics.composer3-ndk_shared", + ], shared_libs: [ "libbase", "libharfbuzz_ng", @@ -106,9 +110,7 @@ cc_defaults { target: { android: { shared_libs: [ - "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.common@1.2", - "android.hardware.graphics.composer3-V1-ndk", "liblog", "libcutils", "libutils", diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 023d6bf0b673..397975dcb78f 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -57,6 +57,49 @@ namespace android { using uirenderer::PaintUtils; +class SkiaCanvas::Clip { +public: + Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m) + : mType(Type::Rect), mOp(op), mMatrix(m), mRRect(SkRRect::MakeRect(rect)) {} + Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m) + : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {} + Clip(const SkPath& path, SkClipOp op, const SkMatrix& m) + : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {} + + void apply(SkCanvas* canvas) const { + canvas->setMatrix(mMatrix); + switch (mType) { + case Type::Rect: + // Don't anti-alias rectangular clips + canvas->clipRect(mRRect.rect(), mOp, false); + break; + case Type::RRect: + // Ensure rounded rectangular clips are anti-aliased + canvas->clipRRect(mRRect, mOp, true); + break; + case Type::Path: + // Ensure path clips are anti-aliased + canvas->clipPath(mPath.value(), mOp, true); + break; + } + } + +private: + enum class Type { + Rect, + RRect, + Path, + }; + + Type mType; + SkClipOp mOp; + SkMatrix mMatrix; + + // These are logically a union (tracked separately due to non-POD path). + std::optional<SkPath> mPath; + SkRRect mRRect; +}; + Canvas* Canvas::create_canvas(const SkBitmap& bitmap) { return new SkiaCanvas(bitmap); } @@ -200,49 +243,6 @@ void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const Paint& paint) { } } -class SkiaCanvas::Clip { -public: - Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m) - : mType(Type::Rect), mOp(op), mMatrix(m), mRRect(SkRRect::MakeRect(rect)) {} - Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m) - : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {} - Clip(const SkPath& path, SkClipOp op, const SkMatrix& m) - : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {} - - void apply(SkCanvas* canvas) const { - canvas->setMatrix(mMatrix); - switch (mType) { - case Type::Rect: - // Don't anti-alias rectangular clips - canvas->clipRect(mRRect.rect(), mOp, false); - break; - case Type::RRect: - // Ensure rounded rectangular clips are anti-aliased - canvas->clipRRect(mRRect, mOp, true); - break; - case Type::Path: - // Ensure path clips are anti-aliased - canvas->clipPath(mPath.value(), mOp, true); - break; - } - } - -private: - enum class Type { - Rect, - RRect, - Path, - }; - - Type mType; - SkClipOp mOp; - SkMatrix mMatrix; - - // These are logically a union (tracked separately due to non-POD path). - std::optional<SkPath> mPath; - SkRRect mRRect; -}; - const SkiaCanvas::SaveRec* SkiaCanvas::currentSaveRec() const { const SaveRec* rec = mSaveStack ? static_cast<const SaveRec*>(mSaveStack->back()) : nullptr; int currentSaveCount = mCanvas->getSaveCount(); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 62e42b8e1863..19cd7bdd6fcb 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -53,8 +53,12 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { } MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { - // TODO: Figure out why this workaround is needed, see b/13913604 - // In the meantime this matches the behavior of GLRenderer, so it is not a regression + // In case the surface was destroyed (e.g. a previous trimMemory call) we + // need to recreate it here. + if (!isSurfaceReady() && mNativeWindow) { + setSurface(mNativeWindow.get(), mSwapBehavior); + } + EGLint error = 0; if (!mEglManager.makeCurrent(mEglSurface, &error)) { return MakeCurrentResult::AlreadyCurrent; @@ -166,6 +170,9 @@ void SkiaOpenGLPipeline::onStop() { } bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { + mNativeWindow = surface; + mSwapBehavior = swapBehavior; + if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; @@ -182,7 +189,8 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); - mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); + const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); + ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior."); return true; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 186998a01745..a80c613697f2 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -61,7 +61,8 @@ protected: private: renderthread::EglManager& mEglManager; EGLSurface mEglSurface = EGL_NO_SURFACE; - bool mBufferPreserved = false; + sp<ANativeWindow> mNativeWindow; + renderthread::SwapBehavior mSwapBehavior = renderthread::SwapBehavior::kSwap_discardBuffer; }; } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 53a4c60cf8a8..f10bca675bf7 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -55,7 +55,12 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() { } MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { - return MakeCurrentResult::AlreadyCurrent; + // In case the surface was destroyed (e.g. a previous trimMemory call) we + // need to recreate it here. + if (!isSurfaceReady() && mNativeWindow) { + setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default); + } + return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; } Frame SkiaVulkanPipeline::getFrame() { @@ -132,7 +137,11 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { +// We can safely ignore the swap behavior because VkManager will always operate +// in a mode equivalent to EGLManager::SwapBehavior::kBufferAge +bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) { + mNativeWindow = surface; + if (mVkSurface) { vulkanManager().destroySurface(mVkSurface); mVkSurface = nullptr; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index d2bdae535f3c..f3d36131a22f 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -66,6 +66,7 @@ private: renderthread::VulkanManager& vulkanManager(); renderthread::VulkanSurface* mVkSurface = nullptr; + sp<ANativeWindow> mNativeWindow; }; } /* namespace skiapipeline */ diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 60ae6044cd5b..7419f8fd89f1 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -404,7 +404,9 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { EXPECT_TRUE(pipeline->isSurfaceReady()); renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); - LOG_ALWAYS_FATAL_IF(pipeline->isSurfaceReady()); + + pipeline->makeCurrent(); + EXPECT_TRUE(pipeline->isSurfaceReady()); } RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { diff --git a/media/Android.bp b/media/Android.bp index 7118afad975a..e97f077b347a 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -35,8 +35,8 @@ aidl_interface { "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl", ], imports: [ - "android.media.audio.common.types", - "android.media.soundtrigger.types", + "android.media.audio.common.types-V2", + "android.media.soundtrigger.types-V1", "media_permission-aidl", ], } @@ -232,12 +232,12 @@ aidl_interface { }, }, imports: [ - "android.media.audio.common.types", + "android.media.audio.common.types-V2", ], versions_with_info: [ { version: "1", - imports: ["android.media.audio.common.types-V1"], + imports: ["android.media.audio.common.types-V2"], }, ], diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index ee2e448bfdac..63173207ec17 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -5092,9 +5092,12 @@ public class MediaPlayer extends PlayerBase @Nullable Map<String, String> optionalParameters) throws NoDrmSchemeException { - Log.v(TAG, "getKeyRequest: " + - " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + - " keyType: " + keyType + " optionalParameters: " + optionalParameters); + Log.v(TAG, "getKeyRequest: " + + " keySetId: " + Arrays.toString(keySetId) + + " initData:" + Arrays.toString(initData) + + " mimeType: " + mimeType + + " keyType: " + keyType + + " optionalParameters: " + optionalParameters); synchronized (mDrmLock) { if (!mActiveDrmScheme) { @@ -5153,7 +5156,8 @@ public class MediaPlayer extends PlayerBase public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) throws NoDrmSchemeException, DeniedByServerException { - Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response); + Log.v(TAG, "provideKeyResponse: keySetId: " + Arrays.toString(keySetId) + + " response: " + Arrays.toString(response)); synchronized (mDrmLock) { @@ -5169,8 +5173,9 @@ public class MediaPlayer extends PlayerBase byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); - Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response + - " --> " + keySetResult); + Log.v(TAG, "provideKeyResponse: keySetId: " + Arrays.toString(keySetId) + + " response: " + Arrays.toString(response) + + " --> " + Arrays.toString(keySetResult)); return keySetResult; @@ -5197,7 +5202,7 @@ public class MediaPlayer extends PlayerBase public void restoreKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException { - Log.v(TAG, "restoreKeys: keySetId: " + keySetId); + Log.v(TAG, "restoreKeys: keySetId: " + Arrays.toString(keySetId)); synchronized (mDrmLock) { @@ -5484,7 +5489,8 @@ public class MediaPlayer extends PlayerBase // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse try { mDrmSessionId = mDrmObj.openSession(); - Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); + Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + + Arrays.toString(mDrmSessionId)); // Sending it down to native/mediaserver to create the crypto object // This call could simply fail due to bad player state, e.g., after start(). @@ -5547,7 +5553,7 @@ public class MediaPlayer extends PlayerBase response = Streams.readFully(connection.getInputStream()); Log.v(TAG, "HandleProvisioninig: Thread run: response " + - response.length + " " + response); + response.length + " " + Arrays.toString(response)); } catch (Exception e) { status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); @@ -5631,8 +5637,9 @@ public class MediaPlayer extends PlayerBase return PREPARE_DRM_STATUS_PREPARATION_ERROR; } - Log.v(TAG, "HandleProvisioninig provReq " + - " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); + Log.v(TAG, "HandleProvisioninig provReq " + + " data: " + Arrays.toString(provReq.getData()) + + " url: " + provReq.getDefaultUrl()); // networking in a background thread mDrmProvisioningInProgress = true; @@ -5715,7 +5722,8 @@ public class MediaPlayer extends PlayerBase private void cleanDrmObj() { // the caller holds mDrmLock - Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); + Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + + " mDrmSessionId=" + Arrays.toString(mDrmSessionId)); if (mDrmSessionId != null) { mDrmObj.closeSession(mDrmSessionId); diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 31d596765bcc..f957498237a8 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -32,6 +32,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -182,7 +183,7 @@ public final class AudioProductStrategy implements Parcelable { AudioProductStrategy thatStrategy = (AudioProductStrategy) o; return mName == thatStrategy.mName && mId == thatStrategy.mId - && mAudioAttributesGroups.equals(thatStrategy.mAudioAttributesGroups); + && Arrays.equals(mAudioAttributesGroups, thatStrategy.mAudioAttributesGroups); } /** @@ -415,7 +416,7 @@ public final class AudioProductStrategy implements Parcelable { return mVolumeGroupId == thatAag.mVolumeGroupId && mLegacyStreamType == thatAag.mLegacyStreamType - && mAudioAttributes.equals(thatAag.mAudioAttributes); + && Arrays.equals(mAudioAttributes, thatAag.mAudioAttributes); } public int getStreamType() { diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java index 79be92214455..d58111d1e9a7 100644 --- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java +++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java @@ -114,7 +114,7 @@ public final class AudioVolumeGroup implements Parcelable { AudioVolumeGroup thatAvg = (AudioVolumeGroup) o; return mName == thatAvg.mName && mId == thatAvg.mId - && mAudioAttributes.equals(thatAvg.mAudioAttributes); + && Arrays.equals(mAudioAttributes, thatAvg.mAudioAttributes); } /** diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java index e71ee204f564..51a2c9d9b50e 100644 --- a/media/java/android/media/metrics/PlaybackMetrics.java +++ b/media/java/android/media/metrics/PlaybackMetrics.java @@ -402,9 +402,10 @@ public final class PlaybackMetrics implements Parcelable { @Override public int hashCode() { return Objects.hash(mMediaDurationMillis, mStreamSource, mStreamType, mPlaybackType, - mDrmType, mContentType, mPlayerName, mPlayerVersion, mExperimentIds, - mVideoFramesPlayed, mVideoFramesDropped, mAudioUnderrunCount, mNetworkBytesRead, - mLocalBytesRead, mNetworkTransferDurationMillis, mDrmSessionId); + mDrmType, mContentType, mPlayerName, mPlayerVersion, + Arrays.hashCode(mExperimentIds), mVideoFramesPlayed, mVideoFramesDropped, + mAudioUnderrunCount, mNetworkBytesRead, mLocalBytesRead, + mNetworkTransferDurationMillis, Arrays.hashCode(mDrmSessionId)); } @Override diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java index 41914b887b2b..ebf1a75a58a4 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java @@ -16,10 +16,6 @@ package com.android.mediaframeworktest.helpers; -import org.hamcrest.CoreMatchers; -import org.hamcrest.Matcher; -import org.junit.rules.ErrorCollector; - import android.graphics.Rect; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; @@ -30,6 +26,10 @@ import android.media.Image; import android.util.Log; import android.util.Size; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matcher; +import org.junit.rules.ErrorCollector; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -903,7 +903,7 @@ public class CameraErrorCollector extends ErrorCollector { if ((value = expectKeyValueNotNull(characteristics, key)) == null) { return; } - String reason = "Key " + key.getName() + " value " + value + String reason = "Key " + key.getName() + " value " + Arrays.toString(value) + " doesn't contain the expected value " + expected; expectContains(reason, value, expected); } @@ -921,7 +921,7 @@ public class CameraErrorCollector extends ErrorCollector { if ((value = expectKeyValueNotNull(characteristics, key)) == null) { return; } - String reason = "Key " + key.getName() + " value " + value + String reason = "Key " + key.getName() + " value " + Arrays.toString(value) + " doesn't contain the expected value " + expected; expectContains(reason, value, expected); } @@ -939,7 +939,7 @@ public class CameraErrorCollector extends ErrorCollector { if ((value = expectKeyValueNotNull(characteristics, key)) == null) { return; } - String reason = "Key " + key.getName() + " value " + value + String reason = "Key " + key.getName() + " value " + Arrays.toString(value) + " doesn't contain the expected value " + expected; expectContains(reason, value, expected); } @@ -960,7 +960,7 @@ public class CameraErrorCollector extends ErrorCollector { public <T> void expectContains(T[] values, T expected) { String reason = "Expected value " + expected - + " is not contained in the given values " + values; + + " is not contained in the given values " + Arrays.toString(values); expectContains(reason, values, expected); } @@ -996,7 +996,7 @@ public class CameraErrorCollector extends ErrorCollector { public void expectContains(int[] values, int expected) { String reason = "Expected value " + expected - + " is not contained in the given values " + values; + + " is not contained in the given values " + Arrays.toString(values); expectContains(reason, values, expected); } @@ -1040,7 +1040,7 @@ public class CameraErrorCollector extends ErrorCollector { */ public void expectContains(boolean[] values, boolean expected) { String reason = "Expected value " + expected - + " is not contained in the given values " + values; + + " is not contained in the given values " + Arrays.toString(values); expectContains(reason, values, expected); } diff --git a/opengl/java/android/opengl/GLLogWrapper.java b/opengl/java/android/opengl/GLLogWrapper.java index bff739636b3e..e645afa1df5a 100644 --- a/opengl/java/android/opengl/GLLogWrapper.java +++ b/opengl/java/android/opengl/GLLogWrapper.java @@ -2812,7 +2812,7 @@ class GLLogWrapper extends GLWrapperBase { public void glDeleteBuffers(int n, int[] buffers, int offset) { begin("glDeleteBuffers"); arg("n", n); - arg("buffers", buffers.toString()); + arg("buffers", Arrays.toString(buffers)); arg("offset", offset); end(); mgl11.glDeleteBuffers(n, buffers, offset); @@ -2831,7 +2831,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGenBuffers(int n, int[] buffers, int offset) { begin("glGenBuffers"); arg("n", n); - arg("buffers", buffers.toString()); + arg("buffers", Arrays.toString(buffers)); arg("offset", offset); end(); mgl11.glGenBuffers(n, buffers, offset); @@ -2850,7 +2850,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetBooleanv(int pname, boolean[] params, int offset) { begin("glGetBooleanv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetBooleanv(pname, params, offset); @@ -2871,7 +2871,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetBufferParameteriv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetBufferParameteriv(target, pname, params, offset); @@ -2891,7 +2891,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetClipPlanef(int pname, float[] eqn, int offset) { begin("glGetClipPlanef"); arg("pname", pname); - arg("eqn", eqn.toString()); + arg("eqn", Arrays.toString(eqn)); arg("offset", offset); end(); mgl11.glGetClipPlanef(pname, eqn, offset); @@ -2910,7 +2910,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetClipPlanex(int pname, int[] eqn, int offset) { begin("glGetClipPlanex"); arg("pname", pname); - arg("eqn", eqn.toString()); + arg("eqn", Arrays.toString(eqn)); arg("offset", offset); end(); mgl11.glGetClipPlanex(pname, eqn, offset); @@ -2928,7 +2928,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetFixedv(int pname, int[] params, int offset) { begin("glGetFixedv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetFixedv(pname, params, offset); @@ -2946,7 +2946,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetFloatv(int pname, float[] params, int offset) { begin("glGetFloatv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetFloatv(pname, params, offset); @@ -2965,7 +2965,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetLightfv"); arg("light", light); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetLightfv(light, pname, params, offset); @@ -2986,7 +2986,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetLightxv"); arg("light", light); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetLightxv(light, pname, params, offset); @@ -3008,7 +3008,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetMaterialfv"); arg("face", face); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetMaterialfv(face, pname, params, offset); @@ -3029,7 +3029,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetMaterialxv"); arg("face", face); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetMaterialxv(face, pname, params, offset); @@ -3050,7 +3050,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexEnviv"); arg("env", env); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetTexEnviv(env, pname, params, offset); @@ -3071,7 +3071,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexEnviv"); arg("env", env); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetTexEnviv(env, pname, params, offset); @@ -3092,7 +3092,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexParameterfv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetTexParameterfv(target, pname, params, offset); @@ -3113,7 +3113,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexParameteriv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetTexEnviv(target, pname, params, offset); @@ -3135,7 +3135,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexParameterxv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glGetTexParameterxv(target, pname, params, offset); @@ -3191,7 +3191,7 @@ class GLLogWrapper extends GLWrapperBase { public void glPointParameterfv(int pname, float[] params, int offset) { begin("glPointParameterfv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glPointParameterfv(pname, params, offset); @@ -3219,7 +3219,7 @@ class GLLogWrapper extends GLWrapperBase { public void glPointParameterxv(int pname, int[] params, int offset) { begin("glPointParameterxv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glPointParameterxv(pname, params, offset); @@ -3259,7 +3259,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexEnviv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glTexEnviv(target, pname, params, offset); @@ -3281,7 +3281,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexParameterfv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glTexParameterfv( target, pname, params, offset); @@ -3313,7 +3313,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexParameterxv"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11.glTexParameterxv(target, pname, params, offset); @@ -3356,7 +3356,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGetPointerv(int pname, Buffer[] params) { begin("glGetPointerv"); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); end(); mgl11.glGetPointerv(pname, params); checkError(); @@ -3513,7 +3513,7 @@ class GLLogWrapper extends GLWrapperBase { public void glDeleteFramebuffersOES(int n, int[] framebuffers, int offset) { begin("glDeleteFramebuffersOES"); arg("n", n); - arg("framebuffers", framebuffers.toString()); + arg("framebuffers", Arrays.toString(framebuffers)); arg("offset", offset); end(); mgl11ExtensionPack.glDeleteFramebuffersOES(n, framebuffers, offset); @@ -3534,7 +3534,7 @@ class GLLogWrapper extends GLWrapperBase { public void glDeleteRenderbuffersOES(int n, int[] renderbuffers, int offset) { begin("glDeleteRenderbuffersOES"); arg("n", n); - arg("renderbuffers", renderbuffers.toString()); + arg("renderbuffers", Arrays.toString(renderbuffers)); arg("offset", offset); end(); mgl11ExtensionPack.glDeleteRenderbuffersOES(n, renderbuffers, offset); @@ -3591,7 +3591,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGenFramebuffersOES(int n, int[] framebuffers, int offset) { begin("glGenFramebuffersOES"); arg("n", n); - arg("framebuffers", framebuffers.toString()); + arg("framebuffers", Arrays.toString(framebuffers)); arg("offset", offset); end(); mgl11ExtensionPack.glGenFramebuffersOES(n, framebuffers, offset); @@ -3612,7 +3612,7 @@ class GLLogWrapper extends GLWrapperBase { public void glGenRenderbuffersOES(int n, int[] renderbuffers, int offset) { begin("glGenRenderbuffersOES"); arg("n", n); - arg("renderbuffers", renderbuffers.toString()); + arg("renderbuffers", Arrays.toString(renderbuffers)); arg("offset", offset); end(); mgl11ExtensionPack.glGenRenderbuffersOES(n, renderbuffers, offset); @@ -3636,7 +3636,7 @@ class GLLogWrapper extends GLWrapperBase { arg("target", target); arg("attachment", attachment); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glGetFramebufferAttachmentParameterivOES(target, attachment, pname, params, offset); @@ -3662,7 +3662,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetRenderbufferParameterivOES"); arg("target", target); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glGetRenderbufferParameterivOES(target, pname, params, offset); @@ -3686,7 +3686,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexGenfv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glGetTexGenfv(coord, pname, params, offset); @@ -3709,7 +3709,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexGeniv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glGetTexGeniv(coord, pname, params, offset); @@ -3732,7 +3732,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glGetTexGenxv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glGetTexGenxv(coord, pname, params, offset); @@ -3799,7 +3799,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexGenfv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glTexGenfv(coord, pname, params, offset); @@ -3833,7 +3833,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexGeniv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glTexGeniv(coord, pname, params, offset); @@ -3867,7 +3867,7 @@ class GLLogWrapper extends GLWrapperBase { begin("glTexGenxv"); arg("coord", coord); arg("pname", pname); - arg("params", params.toString()); + arg("params", Arrays.toString(params)); arg("offset", offset); end(); mgl11ExtensionPack.glTexGenxv(coord, pname, params, offset); diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle index d38013679ad4..5dc2aa0d61cb 100644 --- a/packages/SettingsLib/Spa/build.gradle +++ b/packages/SettingsLib/Spa/build.gradle @@ -22,7 +22,7 @@ buildscript { } } plugins { - id 'com.android.application' version '7.3.0-beta04' apply false - id 'com.android.library' version '7.3.0-beta04' apply false + id 'com.android.application' version '7.3.0-rc01' apply false + id 'com.android.library' version '7.3.0-rc01' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index 3f375345f30f..0d947342c930 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -20,17 +20,21 @@ import com.android.settingslib.spa.framework.common.SettingsPageProviderReposito import com.android.settingslib.spa.gallery.home.HomePageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider -import com.android.settingslib.spa.gallery.page.PreferencePageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider -import com.android.settingslib.spa.gallery.page.SwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider +import com.android.settingslib.spa.gallery.preference.PreferencePageProvider +import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider val galleryPageProviders = SettingsPageProviderRepository( allPagesList = listOf( HomePageProvider, + PreferenceMainPageProvider, PreferencePageProvider, SwitchPreferencePageProvider, + TwoTargetSwitchPreferencePageProvider, ArgumentPageProvider, SliderPageProvider, SpinnerPageProvider, diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index 089920c8e8cf..fdcb1c2a5853 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -25,10 +25,9 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider -import com.android.settingslib.spa.gallery.page.PreferencePageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider -import com.android.settingslib.spa.gallery.page.SwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.widget.scaffold.HomeScaffold @@ -44,8 +43,7 @@ object HomePageProvider : SettingsPageProvider { @Composable private fun HomePage() { HomeScaffold(title = stringResource(R.string.app_name)) { - PreferencePageProvider.EntryItem() - SwitchPreferencePageProvider.EntryItem() + PreferenceMainPageProvider.EntryItem() ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0) SliderPageProvider.EntryItem() diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt index bd366c3d8518..92b64fb42abf 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt @@ -21,6 +21,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavType import androidx.navigation.navArgument +import com.android.settingslib.spa.framework.common.PageArguments +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.toState @@ -41,41 +45,72 @@ object ArgumentPageProvider : SettingsPageProvider { navArgument(INT_PARAM_NAME) { type = NavType.IntType }, ) - @Composable - override fun Page(arguments: Bundle?) { - ArgumentPage( - stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"), - intParam = arguments.getInt(INT_PARAM_NAME), - ) - } + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments) + if (!pageArgs.isValid()) return emptyList() - @Composable - fun EntryItem(stringParam: String, intParam: Int) { - Preference(object : PreferenceModel { - override val title = TITLE - override val summary = - "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState() - override val onClick = navigator("$name/$stringParam/$intParam") - }) + val owner = SettingsPageBuilder.create(name, pageArgs.normalize()).build() + val entryList = mutableListOf<SettingsEntry>() + val stringParamEntry = SettingsEntryBuilder.create("string_param", owner) + stringParamEntry.setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "String param value" + override val summary = pageArgs.getStringArg(STRING_PARAM_NAME)!!.toState() + }) + } + entryList.add(stringParamEntry.build()) + + val intParamEntry = SettingsEntryBuilder.create("int_param", owner) + intParamEntry.setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "Int param value" + override val summary = pageArgs.getIntArg(INT_PARAM_NAME)!!.toString().toState() + }) + } + entryList.add(intParamEntry.build()) + + val entryFoo = buildInjectEntry(pageArgs.buildNextArg("foo")) + val entryBar = buildInjectEntry(pageArgs.buildNextArg("bar")) + if (entryFoo != null) entryList.add(entryFoo.setLink(fromPage = owner).build()) + if (entryBar != null) entryList.add(entryBar.setLink(fromPage = owner).build()) + + return entryList } -} -@Composable -fun ArgumentPage(stringParam: String, intParam: Int) { - RegularScaffold(title = TITLE) { - Preference(object : PreferenceModel { - override val title = "String param value" - override val summary = stringParam.toState() - }) + private fun buildInjectEntry(arguments: Bundle?): SettingsEntryBuilder? { + val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments) + if (!pageArgs.isValid()) return null + + val seBuilder = SettingsEntryBuilder.createLinkTo("injection", name, pageArgs.normalize()) + seBuilder.setIsAllowSearch(false) + + seBuilder.setUiLayoutFn { + val summaryArray = listOf( + "$STRING_PARAM_NAME=" + pageArgs.getStringArg(STRING_PARAM_NAME)!!, + "$INT_PARAM_NAME=" + pageArgs.getIntArg(INT_PARAM_NAME)!! + ) + Preference(object : PreferenceModel { + override val title = TITLE + override val summary = summaryArray.joinToString(", ").toState() + override val onClick = navigator(name + pageArgs.navLink()) + }) + } - Preference(object : PreferenceModel { - override val title = "Int param value" - override val summary = intParam.toString().toState() - }) + return seBuilder + } - ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1) + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + for (entry in buildEntry(arguments)) { + entry.uiLayout() + } + } + } - ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1) + @Composable + fun EntryItem(stringParam: String, intParam: Int) { + buildInjectEntry(buildArgument(stringParam, intParam))?.build()?.uiLayout?.let { it() } } } @@ -83,6 +118,26 @@ fun ArgumentPage(stringParam: String, intParam: Int) { @Composable private fun ArgumentPagePreview() { SettingsTheme { - ArgumentPage(stringParam = "foo", intParam = 0) + ArgumentPageProvider.Page(buildArgument(stringParam = "foo", intParam = 0)) } } + +private fun PageArguments.isValid(): Boolean { + val stringParam = getStringArg(STRING_PARAM_NAME) + return (stringParam != null && listOf("foo", "bar").contains(stringParam)) +} + +private fun PageArguments.buildNextArg(stringParam: String): Bundle { + val intParam = getIntArg(INT_PARAM_NAME) + return if (intParam == null) + buildArgument(stringParam) + else + buildArgument(stringParam, intParam + 1) +} + +private fun buildArgument(stringParam: String, intParam: Int? = null): Bundle { + val args = Bundle() + args.putString(STRING_PARAM_NAME, stringParam) + if (intParam != null) args.putInt(INT_PARAM_NAME, intParam) + return args +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt new file mode 100644 index 000000000000..e71c30cc1f3d --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold + +private const val TITLE = "Category: Preference" + +object PreferenceMainPageProvider : SettingsPageProvider { + override val name = "PreferenceMain" + + @Composable + override fun Page(arguments: Bundle?) { + PreferenceMain() + } + + @Composable + fun EntryItem() { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } +} + +@Composable +private fun PreferenceMain() { + RegularScaffold(title = TITLE) { + PreferencePageProvider.EntryItem() + SwitchPreferencePageProvider.EntryItem() + TwoTargetSwitchPreferencePageProvider.EntryItem() + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt index 90dacdbe5087..36c619f79f5a 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spa.gallery.page +package com.android.settingslib.spa.gallery.preference import android.os.Bundle import androidx.compose.material.icons.Icons diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt index 6d5d4483ab5f..8be6a895f3a4 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spa.gallery.page +package com.android.settingslib.spa.gallery.preference import android.os.Bundle import androidx.compose.runtime.Composable diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt new file mode 100644 index 000000000000..894692be13b8 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel +import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import kotlinx.coroutines.delay + +private const val TITLE = "Sample TwoTargetSwitchPreference" + +object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider { + override val name = "TwoTargetSwitchPreference" + + @Composable + override fun Page(arguments: Bundle?) { + TwoTargetSwitchPreferencePage() + } + + @Composable + fun EntryItem() { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } +} + +@Composable +private fun TwoTargetSwitchPreferencePage() { + RegularScaffold(title = TITLE) { + SampleTwoTargetSwitchPreference() + SampleTwoTargetSwitchPreferenceWithSummary() + SampleTwoTargetSwitchPreferenceWithAsyncSummary() + SampleNotChangeableTwoTargetSwitchPreference() + } +} + +@Composable +private fun SampleTwoTargetSwitchPreference() { + val checked = rememberSaveable { mutableStateOf(false) } + TwoTargetSwitchPreference(remember { + object : SwitchPreferenceModel { + override val title = "TwoTargetSwitchPreference" + override val checked = checked + override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked } + } + }) {} +} + +@Composable +private fun SampleTwoTargetSwitchPreferenceWithSummary() { + val checked = rememberSaveable { mutableStateOf(true) } + TwoTargetSwitchPreference(remember { + object : SwitchPreferenceModel { + override val title = "TwoTargetSwitchPreference" + override val summary = stateOf("With summary") + override val checked = checked + override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked } + } + }) {} +} + +@Composable +private fun SampleTwoTargetSwitchPreferenceWithAsyncSummary() { + val checked = rememberSaveable { mutableStateOf(true) } + val summary = produceState(initialValue = " ") { + delay(1000L) + value = "Async summary" + } + TwoTargetSwitchPreference(remember { + object : SwitchPreferenceModel { + override val title = "TwoTargetSwitchPreference" + override val summary = summary + override val checked = checked + override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked } + } + }) {} +} + +@Composable +private fun SampleNotChangeableTwoTargetSwitchPreference() { + val checked = rememberSaveable { mutableStateOf(true) } + TwoTargetSwitchPreference(remember { + object : SwitchPreferenceModel { + override val title = "TwoTargetSwitchPreference" + override val summary = stateOf("Not changeable") + override val changeable = stateOf(false) + override val checked = checked + override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked } + } + }) {} +} + +@Preview(showBackground = true) +@Composable +private fun TwoTargetSwitchPreferencePagePreview() { + SettingsTheme { + TwoTargetSwitchPreferencePage() + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt new file mode 100644 index 000000000000..c0f585ffa595 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.common + +import android.os.Bundle +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavType + +class PageArguments( + private val navArgsDef: List<NamedNavArgument>, + private val rawArgs: Bundle? +) { + fun normalize(): Bundle { + if (rawArgs == null) return Bundle.EMPTY + val normArgs = Bundle() + for (navArg in navArgsDef) { + when (navArg.argument.type) { + NavType.StringType -> { + val value = rawArgs.getString(navArg.name) + if (value != null) normArgs.putString(navArg.name, value) + } + NavType.IntType -> { + if (rawArgs.containsKey(navArg.name)) + normArgs.putInt(navArg.name, rawArgs.getInt(navArg.name)) + } + } + } + return normArgs + } + + fun navLink(): String{ + if (rawArgs == null) return "" + val argsArray = mutableListOf<String>() + for (navArg in navArgsDef) { + when (navArg.argument.type) { + NavType.StringType -> { + argsArray.add(rawArgs.getString(navArg.name, "")) + } + NavType.IntType -> { + argsArray.add(rawArgs.getInt(navArg.name).toString()) + } + } + } + return argsArray.joinToString("") {arg -> "/$arg" } + } + + fun getStringArg(key: String): String? { + if (rawArgs != null && rawArgs.containsKey(key)) { + return rawArgs.getString(key) + } + return null + } + + fun getIntArg(key: String): Int? { + if (rawArgs != null && rawArgs.containsKey(key)) { + return rawArgs.getInt(key) + } + return null + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt new file mode 100644 index 000000000000..6fcd555f0df6 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.common + +import android.os.Bundle +import androidx.compose.runtime.Composable + +/** + * Defines data of one Settings entry for Settings search. + */ +data class SearchData(val keyword: String = "") + +/** + * Defines data of one Settings entry for UI rendering. + */ +data class UiData(val title: String = "") + +/** + * Defines data to identify a Settings page. + */ +data class SettingsPage(val name: String = "", val args: Bundle = Bundle.EMPTY) + +/** + * Defines data of a Settings entry. + */ +data class SettingsEntry( + val name: String, + val owner: SettingsPage, + + // Generates the unique id of this entry + val uniqueId: String, + + // Defines linking of Settings entries + val fromPage: SettingsPage? = null, + val toPage: SettingsPage? = null, + + /** + * ======================================== + * Defines entry attributes here. + * ======================================== + */ + val isAllowSearch: Boolean, + + /** + * ======================================== + * Defines entry APIs to get data here. + * ======================================== + */ + + /** + * API to get Search related data for this entry. + * Returns null if this entry is not available for the search at the moment. + */ + val searchData: () -> SearchData? = { null }, + + /** + * API to get UI related data for this entry. + * Returns null if the entry is not render-able. + */ + val uiData: () -> UiData? = { null }, + + /** + * API to Render UI of this entry directly. For now, we use it in the internal injection, to + * support the case that the injection page owner wants to maintain both data and UI of the + * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and + * use each entries' UI rendering function in the page instead. + */ + val uiLayout: (@Composable () -> Unit) = {}, +) + +/** + * The helper to build a Settings Page instance. + */ +class SettingsPageBuilder(private val name: String) { + private val arguments = Bundle() + + fun pushArgs(args: Bundle?): SettingsPageBuilder { + if (args != null) arguments.putAll(args) + return this + } + + fun pushArg(argName: String, argValue: String?): SettingsPageBuilder { + if (argValue != null) this.arguments.putString(argName, argValue) + return this + } + + fun pushArg(argName: String, argValue: Int?): SettingsPageBuilder { + if (argValue != null) this.arguments.putInt(argName, argValue) + return this + } + + fun build(): SettingsPage { + return SettingsPage(name, arguments) + } + + companion object { + fun create(name: String, args: Bundle? = null): SettingsPageBuilder { + return SettingsPageBuilder(name).apply { + pushArgs(args) + } + } + } +} + +/** + * The helper to build a Settings Entry instance. + */ +class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) { + private var uniqueId: String? = null + private var fromPage: SettingsPage? = null + private var toPage: SettingsPage? = null + private var isAllowSearch: Boolean? = null + + private var searchDataFn: () -> SearchData? = { null } + private var uiLayoutFn: (@Composable () -> Unit) = {} + + fun build(): SettingsEntry { + return SettingsEntry( + name = name, + owner = owner, + uniqueId = computeUniqueId(), + + // linking data + fromPage = fromPage, + toPage = toPage, + + // attributes + isAllowSearch = computeSearchable(), + + // functions + searchData = searchDataFn, + uiLayout = uiLayoutFn, + ) + } + + fun setLink( + fromPage: SettingsPage? = null, + toPage: SettingsPage? = null + ): SettingsEntryBuilder { + if (fromPage != null) this.fromPage = fromPage + if (toPage != null) this.toPage = toPage + return this + } + + fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder { + this.isAllowSearch = isAllowSearch + return this + } + + fun setSearchDataFn(fn: () -> SearchData?): SettingsEntryBuilder { + this.searchDataFn = fn + return this + } + + fun setUiLayoutFn(fn: @Composable () -> Unit): SettingsEntryBuilder { + this.uiLayoutFn = fn + return this + } + + private fun computeUniqueId(): String = uniqueId ?: (name + owner.toString()) + + private fun computeSearchable(): Boolean = isAllowSearch ?: false + + companion object { + fun create( + entryName: String, + ownerPageName: String, + ownerPageArgs: Bundle? = null + ): SettingsEntryBuilder { + val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs) + return SettingsEntryBuilder(entryName, owner.build()) + } + + fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder { + return SettingsEntryBuilder(entryName, owner) + } + + fun createLinkTo( + entryName: String, + ownerPageName: String, + ownerPageArgs: Bundle? = null + ): SettingsEntryBuilder { + val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs) + return SettingsEntryBuilder(entryName, owner.build()).setLink(toPage = owner.build()) + } + + fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder { + return SettingsEntryBuilder(entryName, owner).setLink(toPage = owner) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt index 2ab0cb9c67b9..a57c66e14c3a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt @@ -36,5 +36,5 @@ interface SettingsPageProvider { @Composable fun Page(arguments: Bundle?) - // fun buildEntry( arguments: Bundle?) : List<entry> + fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList() } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 965436847c64..c1a3c2018186 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -32,6 +32,7 @@ object SettingsDimension { bottom = itemPaddingVertical, ) val itemPaddingAround = 8.dp + val itemDividerHeight = 32.dp /** The size when app icon is displayed in list. */ val appIconItemSize = 32.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt index 04ee3c3be402..11af6ce0beab 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt @@ -19,4 +19,5 @@ package com.android.settingslib.spa.framework.theme object SettingsOpacity { const val Full = 1f const val Disabled = 0.38f + const val Divider = 0.2f } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt new file mode 100644 index 000000000000..507421bf4a97 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.preference + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsOpacity + +@Composable +internal fun TwoTargetPreference( + title: String, + summary: State<String>, + onClick: () -> Unit, + icon: @Composable (() -> Unit)? = null, + widget: @Composable () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = SettingsDimension.itemPaddingEnd), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { + Preference(remember { + object : PreferenceModel { + override val title = title + override val summary = summary + override val icon = icon + override val onClick = onClick + } + }) + } + PreferenceDivider() + widget() + } +} + +@Composable +private fun PreferenceDivider() { + Box( + Modifier + .padding(horizontal = SettingsDimension.itemPaddingEnd) + .size(width = 1.dp, height = SettingsDimension.itemDividerHeight) + .background(color = MaterialTheme.colorScheme.onSurface.copy(SettingsOpacity.Disabled)) + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt new file mode 100644 index 000000000000..f1541b7f4008 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.preference + +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.widget.ui.SettingsSwitch + +@Composable +fun TwoTargetSwitchPreference( + model: SwitchPreferenceModel, + icon: @Composable (() -> Unit)? = null, + onClick: () -> Unit, +) { + TwoTargetPreference( + title = model.title, + summary = model.summary, + onClick = onClick, + icon = icon, + ) { + SettingsSwitch( + checked = model.checked, + changeable = model.changeable, + onCheckedChange = model.onCheckedChange, + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index 8b530b0d7b9b..1af4ce7553d8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme @@ -47,6 +48,8 @@ fun SettingsScaffold( Text( text = title, modifier = Modifier.padding(SettingsDimension.itemPaddingAround), + overflow = TextOverflow.Ellipsis, + maxLines = 1, ) }, navigationIcon = { NavigateBack() }, diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt new file mode 100644 index 000000000000..c49e92d52edb --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.preference + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.isToggleable +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.compose.stateOf +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TwoTargetSwitchPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + val checked = mutableStateOf(false) + composeTestRule.setContent { + TestTwoTargetSwitchPreference(checked = checked, changeable = true) + } + + composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed() + } + + @Test + fun toggleable_initialStateIsCorrect() { + val checked = mutableStateOf(false) + composeTestRule.setContent { + TestTwoTargetSwitchPreference(checked = checked, changeable = true) + } + + composeTestRule.onNode(isToggleable()).assertIsOff() + } + + @Test + fun toggleable_changeable_withEffect() { + val checked = mutableStateOf(false) + composeTestRule.setContent { + TestTwoTargetSwitchPreference(checked = checked, changeable = true) + } + + composeTestRule.onNode(isToggleable()).performClick() + composeTestRule.onNode(isToggleable()).assertIsOn() + } + + @Test + fun toggleable_notChangeable_noEffect() { + val checked = mutableStateOf(false) + composeTestRule.setContent { + TestTwoTargetSwitchPreference(checked = checked, changeable = false) + } + + composeTestRule.onNode(isToggleable()).performClick() + composeTestRule.onNode(isToggleable()).assertIsOff() + } + + @Test + fun clickable_canBeClick() { + val checked = mutableStateOf(false) + var clicked = false + composeTestRule.setContent { + TestTwoTargetSwitchPreference(checked = checked, changeable = false) { + clicked = true + } + } + + composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick() + assertThat(clicked).isTrue() + } +} + +@Composable +private fun TestTwoTargetSwitchPreference( + checked: MutableState<Boolean>, + changeable: Boolean, + onClick: () -> Unit = {}, +) { + TwoTargetSwitchPreference( + model = remember { + object : SwitchPreferenceModel { + override val title = "TwoTargetSwitchPreference" + override val checked = checked + override val changeable = stateOf(changeable) + override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked } + } + }, + onClick = onClick, + ) +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 62c83cf04a4f..51812f043908 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -367,8 +367,14 @@ public class BluetoothEventManager { if (bondState == BluetoothDevice.BOND_NONE) { // Check if we need to remove other Coordinated set member devices / Hearing Aid // devices + if (DEBUG) { + Log.d(TAG, "BondStateChangedHandler: cachedDevice.getGroupId() = " + + cachedDevice.getGroupId() + ", cachedDevice.getHiSyncId()= " + + cachedDevice.getHiSyncId()); + } if (cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID || cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + Log.d(TAG, "BondStateChangedHandler: Start onDeviceUnpaired"); mDeviceManager.onDeviceUnpaired(cachedDevice); } int reason = intent.getIntExtra(BluetoothDevice.EXTRA_UNBOND_REASON, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 32d5b787489a..3ca94dba027e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1431,11 +1431,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> * first connected device in the coordinated set, and then switch the content of the main * device and member devices. * - * @param prevMainDevice the previous Main device, it will be added into the member device set. - * @param newMainDevice the new Main device, it will be removed from the member device set. + * @param newMainDevice the new Main device which is from the previous main device's member + * list. */ - public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice, - CachedBluetoothDevice newMainDevice) { + public void switchMemberDeviceContent(CachedBluetoothDevice newMainDevice) { // Backup from main device final BluetoothDevice tmpDevice = mDevice; final short tmpRssi = mRssi; @@ -1444,8 +1443,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice = newMainDevice.mDevice; mRssi = newMainDevice.mRssi; mJustDiscovered = newMainDevice.mJustDiscovered; - addMemberDevice(prevMainDevice); - mMemberDevices.remove(newMainDevice); + // Set sub device from backup newMainDevice.mDevice = tmpDevice; newMainDevice.mRssi = tmpRssi; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index cd3242a9f7c2..26a208026411 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -317,12 +318,14 @@ public class CachedBluetoothDeviceManager { } public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { + device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device); final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); if (!memberDevices.isEmpty()) { // Main device is unpaired, to unpair the member device for (CachedBluetoothDevice memberDevice : memberDevices) { memberDevice.unpair(); + memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID); device.removeMemberDevice(memberDevice); } } else if (mainDevice != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index fc70ba40fb47..9b38238ca8b8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -231,7 +231,7 @@ public class CsipDeviceManager { // When both LE Audio devices are disconnected, receiving member device // connection. To switch content and dispatch to notify UI change mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); - mainDevice.switchMemberDeviceContent(mainDevice, cachedDevice); + mainDevice.switchMemberDeviceContent(cachedDevice); mainDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device @@ -255,10 +255,11 @@ public class CsipDeviceManager { for (CachedBluetoothDevice device: memberSet) { if (device.isConnected()) { + log("set device: " + device + " as the main device"); // Main device is disconnected and sub device is connected // To copy data from sub device to main device mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); - cachedDevice.switchMemberDeviceContent(device, cachedDevice); + cachedDevice.switchMemberDeviceContent(device); cachedDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index bef1d9cbcde1..62552f914459 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.content.Context; @@ -518,7 +519,8 @@ public class CachedBluetoothDeviceManagerTest { */ @Test public void onDeviceUnpaired_unpairCsipMainDevice() { - when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); cachedDevice1.setGroupId(1); @@ -527,7 +529,12 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice1); + verify(mDevice2).removeBond(); + assertThat(cachedDevice1.getGroupId()).isEqualTo( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + assertThat(cachedDevice2.getGroupId()).isEqualTo( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); } /** @@ -536,6 +543,7 @@ public class CachedBluetoothDeviceManagerTest { @Test public void onDeviceUnpaired_unpairCsipSubDevice() { when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); cachedDevice1.setGroupId(1); @@ -544,7 +552,10 @@ public class CachedBluetoothDeviceManagerTest { // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); + verify(mDevice1).removeBond(); + assertThat(cachedDevice2.getGroupId()).isEqualTo( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 76c066cd029e..79e99387b2fa 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -1050,4 +1050,23 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.mDrawableCache.size()).isEqualTo(0); } + + @Test + public void switchMemberDeviceContent_switchMainDevice_switchesSuccessful() { + mCachedDevice.mRssi = RSSI_1; + mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1; + mSubCachedDevice.mRssi = RSSI_2; + mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2; + mCachedDevice.addMemberDevice(mSubCachedDevice); + + mCachedDevice.switchMemberDeviceContent(mSubCachedDevice); + + assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2); + assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2); + assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice); + assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1); + assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1); + assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice); + assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue(); + } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index ba7a9bcc7cd7..1dfa96f55532 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -31,7 +31,6 @@ import android.provider.settings.backup.SecureSettings; import android.provider.settings.backup.SystemSettings; import androidx.test.filters.SmallTest; -import androidx.test.filters.Suppress; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; @@ -709,7 +708,6 @@ public class SettingsBackupTest { Settings.Secure.DOCKED_CLOCK_FACE, Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, - Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, @@ -837,15 +835,66 @@ public class SettingsBackupTest { } @Test - @Suppress //("b/148236308") public void secureSettingsBackedUpOrDenied() { + // List of settings that were not added to either SETTINGS_TO_BACKUP or + // BACKUP_DENY_LIST_SECURE_SETTINGS while this test was suppressed in + // the last two years. Settings in this list are temporarily allowed to + // not be explicitly listed as backed up or denied so we can re-enable + // this test. + // + // DO NOT ADD NEW SETTINGS TO THIS LIST! + Set<String> settingsNotBackedUpOrDeniedTemporaryAllowList = + newHashSet( + Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, + Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT, + Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY, + Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, + Settings.Secure.AUTO_REVOKE_DISABLED, + Settings.Secure.BIOMETRIC_APP_ENABLED, + Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED, + Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, + Settings.Secure.BLUETOOTH_ADDR_VALID, + Settings.Secure.BLUETOOTH_ADDRESS, + Settings.Secure.BLUETOOTH_NAME, + Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS, + Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, + Settings.Secure.COMMUNAL_MODE_ENABLED, + Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, + Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, + Settings.Secure.DOCK_SETUP_STATE, + Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE, + Settings.Secure.GAME_DASHBOARD_ALWAYS_ON, + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, + Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, + Settings.Secure.LOCATION_COARSE_ACCURACY_M, + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, + Settings.Secure.NAS_SETTINGS_UPDATED, + Settings.Secure.NAV_BAR_FORCE_VISIBLE, + Settings.Secure.NAV_BAR_KIDS_MODE, + Settings.Secure.NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT, + Settings.Secure.NEARBY_SHARING_SLICE_URI, + Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES, + Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, + Settings.Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL, + Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, + Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING, + Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION, + Settings.Secure.SPATIAL_AUDIO_ENABLED, + Settings.Secure.TIMEOUT_TO_USER_ZERO, + Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED, + Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF, + Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON); + HashSet<String> keys = new HashSet<String>(); Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP); Collections.addAll(keys, DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); - checkSettingsBackedUpOrDenied( - getCandidateSettings(Settings.Secure.class), - keys, - BACKUP_DENY_LIST_SECURE_SETTINGS); + + Set<String> allSettings = getCandidateSettings(Settings.Secure.class); + allSettings.removeAll(settingsNotBackedUpOrDeniedTemporaryAllowList); + + checkSettingsBackedUpOrDenied(allSettings, keys, BACKUP_DENY_LIST_SECURE_SETTINGS); } private static void checkSettingsBackedUpOrDenied( diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index abcd65b5071a..7649de649cf8 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -94,6 +94,7 @@ <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" /> <!-- ActivityManager --> <uses-permission android:name="android.permission.REAL_GET_TASKS" /> diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt new file mode 100644 index 000000000000..3ca8dfe4fd38 --- /dev/null +++ b/packages/SystemUI/ktfmt_includes.txt @@ -0,0 +1,870 @@ ++packages/SystemUI +-packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +-packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +-packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt +-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +-packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt +-packages/SystemUI/checks/tests/com/android/systemui/lint/BroadcastSentViaContextDetectorTest.kt +-packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt +-packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt +-packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt +-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +-packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +-packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt +-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +-packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt +-packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt +-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt +-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt +-packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt +-packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt +-packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt +-packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt +-packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt +-packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt +-packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +-packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt +-packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt +-packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +-packages/SystemUI/src/com/android/keyguard/KeyguardListenQueue.kt +-packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +-packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt +-packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt +-packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +-packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt +-packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt +-packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt +-packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt +-packages/SystemUI/src/com/android/systemui/ChooserSelector.kt +-packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt +-packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +-packages/SystemUI/src/com/android/systemui/DualToneHandler.kt +-packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +-packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +-packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt +-packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt +-packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt +-packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt +-packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt +-packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt +-packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt +-packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt +-packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +-packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +-packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt +-packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +-packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt +-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt +-packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt +-packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt +-packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +-packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt +-packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +-packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt +-packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt +-packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +-packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +-packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt +-packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +-packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt +-packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapper.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt +-packages/SystemUI/src/com/android/systemui/controls/controller/StatefulControlSubscriber.kt +-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt +-packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +-packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +-packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt +-packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt +-packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt +-packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt +-packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt +-packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +-packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +-packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt +-packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +-packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +-packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt +-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt +-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt +-packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +-packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt +-packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt +-packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +-packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt +-packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt +-packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +-packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +-packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt +-packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt +-packages/SystemUI/src/com/android/systemui/dump/LogBufferFreezer.kt +-packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt +-packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt +-packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +-packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +-packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +-packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +-packages/SystemUI/src/com/android/systemui/log/LogLevel.kt +-packages/SystemUI/src/com/android/systemui/log/LogMessage.kt +-packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt +-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt +-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +-packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt +-packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +-packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt +-packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt +-packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +-packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt +-packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt +-packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +-packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt +-packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +-packages/SystemUI/src/com/android/systemui/media/MediaData.kt +-packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +-packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +-packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +-packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +-packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt +-packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt +-packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +-packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +-packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt +-packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +-packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt +-packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +-packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt +-packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt +-packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +-packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt +-packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt +-packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +-packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt +-packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt +-packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt +-packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +-packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt +-packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +-packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +-packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +-packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt +-packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt +-packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt +-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt +-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt +-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt +-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt +-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt +-packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt +-packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt +-packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt +-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt +-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt +-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +-packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt +-packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt +-packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt +-packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +-packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +-packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +-packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt +-packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +-packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +-packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +-packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +-packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +-packages/SystemUI/src/com/android/systemui/qs/QSExpansionPathInterpolator.kt +-packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +-packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt +-packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt +-packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +-packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt +-packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt +-packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt +-packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt +-packages/SystemUI/src/com/android/systemui/qs/external/QSExternalModule.kt +-packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt +-packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt +-packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +-packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +-packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt +-packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt +-packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +-packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +-packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +-packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +-packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +-packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +-packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt +-packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +-packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt +-packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt +-packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt +-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt +-packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +-packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt +-packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +-packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt +-packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt +-packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt +-packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt +-packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +-packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +-packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt +-packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt +-packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt +-packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt +-packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +-packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt +-packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt +-packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt +-packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +-packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +-packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +-packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt +-packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt +-packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt +-packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt +-packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt +-packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +-packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +-packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt +-packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt +-packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt +-packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +-packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +-packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt +-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +-packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt +-packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt +-packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +-packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt +-packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt +-packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt +-packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt +-packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/FeedbackIcon.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/RemoteInputViewModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarIconBlocklist.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyState.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletController.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt +-packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt +-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt +-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +-packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt +-packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +-packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt +-packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt +-packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +-packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt +-packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +-packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt +-packages/SystemUI/src/com/android/systemui/user/UserCreator.kt +-packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +-packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt +-packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt +-packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt +-packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt +-packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt +-packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt +-packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt +-packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt +-packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt +-packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt +-packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt +-packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt +-packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt +-packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt +-packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt +-packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt +-packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt +-packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt +-packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt +-packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt +-packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt +-packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt +-packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt +-packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +-packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt +-packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt +-packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt +-packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt +-packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt +-packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt +-packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt +-packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +-packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt +-packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt +-packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt +-packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt +-packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt +-packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt +-packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt +-packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt +-packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt +-packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +-packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt +-packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt +-packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +-packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt +-packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/DeletionJobServiceTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt +-packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt +-packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt +-packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +-packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +-packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +-packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt +-packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt +-packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt +-packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +-packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManagerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt +-packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt +-packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt +-packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +-packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +-packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt +-packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt +-packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +-packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +-packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt +-packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt +-packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 8fa22048cd97..d90156d451c7 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -217,7 +217,7 @@ the force lock button. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_user_request">Device was locked manually</string> - <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] --> + <!-- Face hint message when face was not recognized. [CHAR LIMIT=20] --> <string name="kg_face_not_recognized">Not recognized</string> <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=53] --> diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml new file mode 100644 index 000000000000..50f3ffcaf968 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<ImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/media_entry_chip" + android:layout_height="@dimen/keyguard_affordance_fixed_height" + android:layout_width="@dimen/keyguard_affordance_fixed_width" + android:layout_gravity="bottom|start" + android:scaleType="center" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_music_note" + android:background="@drawable/keyguard_bottom_affordance_bg" + android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset" + android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" + android:contentDescription="@string/controls_media_title" /> diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index 921f78830981..73050c253d72 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -23,4 +23,4 @@ app:lottie_autoPlay="true" app:lottie_loop="true" app:lottie_rawRes="@raw/sfps_pulse" - android:contentDescription="@string/accessibility_fingerprint_label"/> + android:importantForAccessibility="no"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index dca5ea836fa4..f88f46f2a3c8 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -131,7 +131,11 @@ <!-- For StatusIconContainer to tag its icon views --> <item type="id" name="status_bar_view_state_tag" /> + <!-- Default display cutout on the physical top of screen --> <item type="id" name="display_cutout" /> + <item type="id" name="display_cutout_left" /> + <item type="id" name="display_cutout_right" /> + <item type="id" name="display_cutout_bottom" /> <item type="id" name="row_tag_for_content_view" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c7b2ff34c55f..d5a744bc29d6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -389,6 +389,10 @@ <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string> <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] --> <string name="keyguard_face_failed_use_fp">@string/fingerprint_dialog_use_fingerprint_instead</string> + <!-- Message shown to inform the user a face cannot be recognized. [CHAR LIMIT=25] --> + <string name="keyguard_face_failed">Can\u2019t recognize face</string> + <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] --> + <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string> <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_bluetooth_connected">Bluetooth connected.</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 3517d22ae50d..d0baf3dad79d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -89,7 +89,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter; -import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord; +import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index fdde40296511..0a82968ae4cb 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -19,7 +19,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; -import android.graphics.PointF; +import android.graphics.Point; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -54,7 +54,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private boolean mAod; @NonNull private final RectF mSensorRect; - @NonNull private PointF mLockIconCenter = new PointF(0f, 0f); + @NonNull private Point mLockIconCenter = new Point(0, 0); private float mRadius; private int mLockIconPadding; @@ -126,7 +126,7 @@ public class LockIconView extends FrameLayout implements Dumpable { * Set the location of the lock icon. */ @VisibleForTesting - public void setCenterLocation(@NonNull PointF center, float radius, int drawablePadding) { + public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; mLockIconPadding = drawablePadding; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index d6974dfac570..a6b8841402b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -27,7 +27,7 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.PointF; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricAuthenticator; @@ -359,8 +359,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mAuthController.getUdfpsRadius(), scaledPadding); } else { mView.setCenterLocation( - new PointF(mWidthPixels / 2, - mHeightPixels - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor)), + new Point((int) mWidthPixels / 2, + (int) (mHeightPixels + - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), sLockIconRadiusPx * scaleFactor, scaledPadding); } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 4c400a81024f..2e13903814a5 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -72,6 +72,7 @@ import com.android.settingslib.Utils; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; @@ -118,6 +119,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private static final boolean VERBOSE = false; static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS; + private static final int[] DISPLAY_CUTOUT_IDS = { + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom + }; + private DisplayManager mDisplayManager; @VisibleForTesting protected boolean mIsRegistered; @@ -139,13 +147,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab protected RoundedCornerResDelegate mRoundedCornerResDelegate; @VisibleForTesting protected DecorProviderFactory mRoundedCornerFactory; + private CutoutDecorProviderFactory mCutoutFactory; private int mProviderRefreshToken = 0; @VisibleForTesting protected OverlayWindow[] mOverlays = null; @VisibleForTesting - @Nullable - DisplayCutoutView[] mCutoutViews; - @VisibleForTesting ViewGroup mScreenDecorHwcWindow; @VisibleForTesting ScreenDecorHwcLayer mScreenDecorHwcLayer; @@ -187,18 +193,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - if (mCutoutViews == null) { - Log.w(TAG, "DisplayCutoutView not initialized onApplyCameraProtection"); - return; - } - - // Show the extra protection around the front facing camera if necessary - for (DisplayCutoutView dcv : mCutoutViews) { - // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile - if (dcv != null) { - dcv.setProtection(protectionPath, bounds); - dcv.enableShowProtection(true); + int setProtectionCnt = 0; + for (int id: DISPLAY_CUTOUT_IDS) { + final View view = getOverlayView(id); + if (!(view instanceof DisplayCutoutView)) { + continue; } + ++setProtectionCnt; + final DisplayCutoutView dcv = (DisplayCutoutView) view; + dcv.setProtection(protectionPath, bounds); + dcv.enableShowProtection(true); + } + if (setProtectionCnt == 0) { + Log.e(TAG, "CutoutView not initialized showCameraProtection"); } } @@ -219,16 +226,17 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - if (mCutoutViews == null) { - Log.w(TAG, "DisplayCutoutView not initialized onHideCameraProtection"); - return; - } - // Go back to the regular anti-aliasing - for (DisplayCutoutView dcv : mCutoutViews) { - // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile - if (dcv != null) { - dcv.enableShowProtection(false); + int setProtectionCnt = 0; + for (int id: DISPLAY_CUTOUT_IDS) { + final View view = getOverlayView(id); + if (!(view instanceof DisplayCutoutView)) { + continue; } + ++setProtectionCnt; + ((DisplayCutoutView) view).enableShowProtection(false); + } + if (setProtectionCnt == 0) { + Log.e(TAG, "CutoutView not initialized hideCameraProtection"); } } @@ -335,6 +343,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab decorProviders.addAll(mFaceScanningFactory.getProviders()); if (!hasHwLayer) { decorProviders.addAll(mRoundedCornerFactory.getProviders()); + decorProviders.addAll(mCutoutFactory.getProviders()); } return decorProviders; } @@ -379,6 +388,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); + mCutoutFactory = getCutoutFactory(); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); updateHwLayerRoundedCornerDrawable(); setupDecorations(); @@ -483,18 +493,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (needToUpdateProviderViews) { updateOverlayProviderViews(null); } else { - updateOverlayProviderViews(new Integer[] { mFaceScanningViewId }); - } - - if (mCutoutViews != null) { - final int size = mCutoutViews.length; - for (int i = 0; i < size; i++) { - final DisplayCutoutView cutoutView = mCutoutViews[i]; - if (cutoutView == null) { - continue; - } - cutoutView.onDisplayChanged(newUniqueId); - } + updateOverlayProviderViews(new Integer[] { + mFaceScanningViewId, + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom, + }); } if (mScreenDecorHwcLayer != null) { @@ -507,8 +512,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateConfiguration(); } + @VisibleForTesting @Nullable - private View getOverlayView(@IdRes int id) { + View getOverlayView(@IdRes int id) { if (mOverlays == null) { return null; } @@ -565,18 +571,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab removeHwcOverlay(); } - final DisplayCutout cutout = getCutout(); + boolean[] hasCreatedOverlay = new boolean[BOUNDS_POSITION_LENGTH]; final boolean shouldOptimizeVisibility = shouldOptimizeVisibility(); + Integer bound; + while ((bound = DecorProviderKt.getProperBound(decorProviders)) != null) { + hasCreatedOverlay[bound] = true; + Pair<List<DecorProvider>, List<DecorProvider>> pair = + DecorProviderKt.partitionAlignedBound(decorProviders, bound); + decorProviders = pair.getSecond(); + createOverlay(bound, pair.getFirst(), shouldOptimizeVisibility); + } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (shouldShowSwLayerCutout(i, cutout) - || shouldShowSwLayerFaceScan(i, cutout) - || shouldShowSwLayerRoundedCorner(i, cutout) - || shouldShowSwLayerPrivacyDot(i, cutout)) { - Pair<List<DecorProvider>, List<DecorProvider>> pair = - DecorProviderKt.partitionAlignedBound(decorProviders, i); - decorProviders = pair.getSecond(); - createOverlay(i, pair.getFirst(), shouldOptimizeVisibility); - } else { + if (!hasCreatedOverlay[i]) { removeOverlay(i); } } @@ -639,9 +645,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - @VisibleForTesting - DisplayCutout getCutout() { - return mContext.getDisplay().getCutout(); + // For unit test to override + protected CutoutDecorProviderFactory getCutoutFactory() { + return new CutoutDecorProviderFactory(mContext.getResources(), + mContext.getDisplay()); } @VisibleForTesting @@ -731,16 +738,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab overlayView.setAlpha(0); overlayView.setForceDarkAllowed(false); - // Only show cutout in mOverlays when hwc doesn't support screen decoration - if (mHwcScreenDecorationSupport == null) { - if (mCutoutViews == null) { - mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; - } - mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); - overlayView.addView(mCutoutViews[pos]); - mCutoutViews[pos].updateRotation(mRotation); - } - mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @@ -920,6 +917,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private void setupCameraListener() { + // TODO(b/238143614) Support dual screen camera protection Resources res = mContext.getResources(); boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection); if (enabled) { @@ -948,27 +946,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mTintColor = Color.RED; } - if (mOverlays == null) { - return; - } - - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { - continue; - } - final ViewGroup overlayView = mOverlays[i].getRootView(); - final int size = overlayView.getChildCount(); - View child; - for (int j = 0; j < size; j++) { - child = overlayView.getChildAt(j); - if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) { - ((DisplayCutoutView) child).setColor(mTintColor); - } - } - } - updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom, R.id.rounded_corner_top_left, R.id.rounded_corner_top_right, R.id.rounded_corner_bottom_left, @@ -1093,15 +1076,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateHwLayerRoundedCornerDrawable(); } updateLayoutParams(); - // update cutout view rotation - if (mCutoutViews != null) { - for (final DisplayCutoutView cutoutView: mCutoutViews) { - if (cutoutView == null) { - continue; - } - cutoutView.updateRotation(mRotation); - } - } // update all provider views inside overlay updateOverlayProviderViews(null); @@ -1120,46 +1094,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return mRoundedCornerFactory.getHasProviders(); } - private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - // for cutout is null or cutout with only waterfall. - final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty(); - // Shows rounded corner on left and right overlays only when there is no top or bottom - // cutout. - final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation); - final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation); - if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty() - || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) { - return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM; - } else { - return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT; - } - } - - private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) - && mHwcScreenDecorationSupport == null; - } - - private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); - } - - private boolean shouldShowSwLayerFaceScan(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return mFaceScanningFactory.getHasProviders() && isDefaultShownOverlayPos(pos, cutout); - } - - private boolean shouldShowSwLayerCutout(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); - final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); - return (bounds != null && !bounds[rotatedPos].isEmpty() - && mHwcScreenDecorationSupport == null); - } - private boolean shouldOptimizeVisibility() { return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders()) && (mHwcScreenDecorationSupport != null @@ -1168,7 +1102,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean shouldDrawCutout() { - return shouldDrawCutout(mContext); + return mCutoutFactory.getHasProviders(); } static boolean shouldDrawCutout(Context context) { @@ -1284,7 +1218,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab paint.setColor(mColor); paint.setStyle(Paint.Style.FILL); - setId(R.id.display_cutout); if (DEBUG) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); @@ -1292,6 +1225,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } public void setColor(int color) { + if (color == mColor) { + return; + } mColor = color; paint.setColor(mColor); invalidate(); @@ -1299,6 +1235,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @Override public void updateRotation(int rotation) { + // updateRotation() is called inside CutoutDecorProviderImpl::onReloadResAndMeasure() + // during onDisplayChanged. In order to prevent reloading cutout info in super class, + // check mRotation at first + if (rotation == mRotation) { + return; + } mRotation = rotation; super.updateRotation(rotation); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 84e1c3d4c8f0..436b756ea0cb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -129,6 +129,8 @@ public class AuthContainerView extends LinearLayout private final Set<Integer> mFailedModalities = new HashSet<Integer>(); private final @Background DelayableExecutor mBackgroundExecutor; + private int mOrientation; + private boolean mSkipFirstLostFocus = false; // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; @@ -441,6 +443,12 @@ public class AuthContainerView extends LinearLayout public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (!hasWindowFocus) { + //it's a workaround to avoid closing BP incorrectly + //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true) + if (mSkipFirstLostFocus) { + mSkipFirstLostFocus = false; + return; + } Log.v(TAG, "Lost window focus, dismissing the dialog"); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); } @@ -450,6 +458,9 @@ public class AuthContainerView extends LinearLayout public void onAttachedToWindow() { super.onAttachedToWindow(); + //save the first orientation + mOrientation = getResources().getConfiguration().orientation; + mWakefulnessLifecycle.addObserver(this); if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { @@ -621,6 +632,12 @@ public class AuthContainerView extends LinearLayout if (mBiometricView != null) { mBiometricView.restoreState(savedState); } + + if (savedState != null) { + mSkipFirstLostFocus = savedState.getBoolean( + AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED); + } + wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle())); } @@ -640,30 +657,50 @@ public class AuthContainerView extends LinearLayout @Override public void onAuthenticationSucceeded(@Modality int modality) { - mBiometricView.onAuthenticationSucceeded(modality); + if (mBiometricView != null) { + mBiometricView.onAuthenticationSucceeded(modality); + } else { + Log.e(TAG, "onAuthenticationSucceeded(): mBiometricView is null"); + } } @Override public void onAuthenticationFailed(@Modality int modality, String failureReason) { - mFailedModalities.add(modality); - mBiometricView.onAuthenticationFailed(modality, failureReason); + if (mBiometricView != null) { + mFailedModalities.add(modality); + mBiometricView.onAuthenticationFailed(modality, failureReason); + } else { + Log.e(TAG, "onAuthenticationFailed(): mBiometricView is null"); + } } @Override public void onHelp(@Modality int modality, String help) { - mBiometricView.onHelp(modality, help); + if (mBiometricView != null) { + mBiometricView.onHelp(modality, help); + } else { + Log.e(TAG, "onHelp(): mBiometricView is null"); + } } @Override public void onError(@Modality int modality, String error) { - mBiometricView.onError(modality, error); + if (mBiometricView != null) { + mBiometricView.onError(modality, error); + } else { + Log.e(TAG, "onError(): mBiometricView is null"); + } } @Override public void onPointerDown() { - if (mBiometricView.onPointerDown(mFailedModalities)) { - Log.d(TAG, "retrying failed modalities (pointer down)"); - mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); + if (mBiometricView != null) { + if (mBiometricView.onPointerDown(mFailedModalities)) { + Log.d(TAG, "retrying failed modalities (pointer down)"); + mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); + } + } else { + Log.e(TAG, "onPointerDown(): mBiometricView is null"); } } @@ -677,6 +714,10 @@ public class AuthContainerView extends LinearLayout mBiometricView != null && mCredentialView == null); outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null); + if (mOrientation != getResources().getConfiguration().orientation) { + outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, true); + } + if (mBiometricView != null) { mBiometricView.onSaveState(outState); } @@ -694,7 +735,11 @@ public class AuthContainerView extends LinearLayout @Override public void animateToCredentialUI() { - mBiometricView.startTransitionToCredentialUI(); + if (mBiometricView != null) { + mBiometricView.startTransitionToCredentialUI(); + } else { + Log.e(TAG, "animateToCredentialUI(): mBiometricView is null"); + } } void animateAway(@AuthDialogCallback.DismissedReason int reason) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 282f25104c44..35269bca7365 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -19,6 +19,9 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -31,7 +34,6 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; @@ -56,7 +58,9 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; import android.util.Log; +import android.util.RotationUtils; import android.util.SparseBooleanArray; +import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.WindowManager; @@ -117,8 +121,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull private Point mStableDisplaySize = new Point(); - @Nullable private final PointF mFaceAuthSensorLocation; - @Nullable private PointF mFingerprintLocation; + private final Display mDisplay; + private float mScaleFactor = 1f; + // sensor locations without any resolution scaling nor rotation adjustments: + @Nullable private final Point mFaceSensorLocationDefault; + @Nullable private final Point mFingerprintSensorLocationDefault; + // cached sensor locations: + @Nullable private Point mFaceSensorLocation; + @Nullable private Point mFingerprintSensorLocation; @Nullable private Rect mUdfpsBounds; private final Set<Callback> mCallbacks = new HashSet<>(); @@ -148,6 +158,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull private final LockPatternUtils mLockPatternUtils; @NonNull private final InteractionJankMonitor mInteractionJankMonitor; private final @Background DelayableExecutor mBackgroundExecutor; + private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); @VisibleForTesting final TaskStackListener mTaskStackListener = new TaskStackListener() { @@ -166,7 +177,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received"); mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; - mOrientationListener.disable(); for (Callback cb : mCallbacks) { cb.onBiometricPromptDismissed(); @@ -200,7 +210,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba Log.e(TAG, "Evicting client due to: " + topPackage); mCurrentDialog.dismissWithoutCallback(true /* animate */); mCurrentDialog = null; - mOrientationListener.disable(); for (Callback cb : mCallbacks) { cb.onBiometricPromptDismissed(); @@ -266,7 +275,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); - updateUdfpsLocation(); } mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; @@ -281,7 +289,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba TYPE_FINGERPRINT, userId, sensorId, hasEnrollments)); } }); - updateFingerprintLocation(); + updateSensorLocations(); for (Callback cb : mCallbacks) { cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); @@ -479,11 +487,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba /** * @return where the UDFPS exists on the screen in pixels in portrait mode. */ - @Nullable public PointF getUdfpsLocation() { + @Nullable public Point getUdfpsLocation() { if (mUdfpsController == null || mUdfpsBounds == null) { return null; } - return new PointF(mUdfpsBounds.centerX(), mUdfpsBounds.centerY()); + return new Point(mUdfpsBounds.centerX(), mUdfpsBounds.centerY()); } /** @@ -497,45 +505,105 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } /** - * @return the scale factor representing the user's current resolution / the stable - * (default) resolution + * Gets the cached scale factor representing the user's current resolution / the stable + * (default) resolution. */ public float getScaleFactor() { - if (mUdfpsController == null || mUdfpsController.mOverlayParams == null) { - return 1f; - } - return mUdfpsController.mOverlayParams.getScaleFactor(); + return mScaleFactor; } /** - * @return where the fingerprint sensor exists in pixels in portrait mode. devices without an - * overridden value will use the default value even if they don't have a fingerprint sensor + * Updates the current display info and cached scale factor & sensor locations. + * Getting the display info is a relatively expensive call, so avoid superfluous calls. + */ + private void updateSensorLocations() { + mDisplay.getDisplayInfo(mCachedDisplayInfo); + + final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio( + mStableDisplaySize.x, mStableDisplaySize.y, mCachedDisplayInfo.getNaturalWidth(), + mCachedDisplayInfo.getNaturalHeight()); + if (scaleFactor == Float.POSITIVE_INFINITY) { + mScaleFactor = 1f; + } else { + mScaleFactor = scaleFactor; + } + + updateUdfpsLocation(); + updateFingerprintLocation(); + updateFaceLocation(); + } + /** + * @return where the fingerprint sensor exists in pixels in its natural orientation. + * Devices without location configs will use the default value even if they don't have a + * fingerprint sensor. + * + * May return null if the fingerprint sensor isn't available yet. */ - @Nullable public PointF getFingerprintSensorLocation() { + @Nullable private Point getFingerprintSensorLocationInNaturalOrientation() { if (getUdfpsLocation() != null) { return getUdfpsLocation(); } - return mFingerprintLocation; + return new Point( + (int) (mFingerprintSensorLocationDefault.x * mScaleFactor), + (int) (mFingerprintSensorLocationDefault.y * mScaleFactor) + ); } /** - * @return where the face authentication sensor exists relative to the screen in pixels in - * portrait mode. + * @return where the fingerprint sensor exists in pixels exists the current device orientation. + * Devices without location configs will use the default value even if they don't have a + * fingerprint sensor. */ - @Nullable public PointF getFaceAuthSensorLocation() { - if (mFaceProps == null || mFaceAuthSensorLocation == null) { - return null; + @Nullable public Point getFingerprintSensorLocation() { + return mFingerprintSensorLocation; + } + + private void updateFingerprintLocation() { + if (mFpProps == null) { + mFingerprintSensorLocation = null; + } else { + mFingerprintSensorLocation = rotateToCurrentOrientation( + getFingerprintSensorLocationInNaturalOrientation(), + mCachedDisplayInfo); } - DisplayInfo displayInfo = new DisplayInfo(); - mContext.getDisplay().getDisplayInfo(displayInfo); - final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio( - mStableDisplaySize.x, mStableDisplaySize.y, displayInfo.getNaturalWidth(), - displayInfo.getNaturalHeight()); - if (scaleFactor == Float.POSITIVE_INFINITY) { - return new PointF(mFaceAuthSensorLocation.x, mFaceAuthSensorLocation.y); + } + + /** + * @return where the face sensor exists in pixels in the current device orientation. Returns + * null if no face sensor exists. + */ + @Nullable public Point getFaceSensorLocation() { + return mFaceSensorLocation; + } + + private void updateFaceLocation() { + if (mFaceProps == null || mFaceSensorLocationDefault == null) { + mFaceSensorLocation = null; + } else { + mFaceSensorLocation = rotateToCurrentOrientation( + new Point( + (int) (mFaceSensorLocationDefault.x * mScaleFactor), + (int) (mFaceSensorLocationDefault.y * mScaleFactor)), + mCachedDisplayInfo + ); } - return new PointF(mFaceAuthSensorLocation.x * scaleFactor, - mFaceAuthSensorLocation.y * scaleFactor); + } + + /** + * @param inOutPoint point on the display in pixels. Going in, represents the point + * in the device's natural orientation. Going out, represents + * the point in the display's current orientation. + * @param displayInfo currently display information to use to rotate the point + */ + @VisibleForTesting + protected Point rotateToCurrentOrientation(Point inOutPoint, DisplayInfo displayInfo) { + RotationUtils.rotatePoint( + inOutPoint, + displayInfo.rotation, + displayInfo.getNaturalWidth(), + displayInfo.getNaturalHeight() + ); + return inOutPoint; } /** @@ -599,7 +667,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Background DelayableExecutor bgExecutor) { super(context); mExecution = execution; - mWakefulnessLifecycle = wakefulnessLifecycle; mUserManager = userManager; mLockPatternUtils = lockPatternUtils; mHandler = handler; @@ -625,54 +692,58 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba return Unit.INSTANCE; }); + mWakefulnessLifecycle = wakefulnessLifecycle; + mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedWakingUp() { + notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_AWAKE); + } + + @Override + public void onStartedGoingToSleep() { + notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_GOING_TO_SLEEP); + } + }); + mStatusBarStateController = statusBarStateController; mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() { @Override public void onDozingChanged(boolean isDozing) { - notifyDozeChanged(isDozing); + notifyDozeChanged(isDozing, wakefulnessLifecycle.getWakefulness()); } }); mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; - int[] faceAuthLocation = context.getResources().getIntArray( com.android.systemui.R.array.config_face_auth_props); if (faceAuthLocation == null || faceAuthLocation.length < 2) { - mFaceAuthSensorLocation = null; + mFaceSensorLocationDefault = null; } else { - mFaceAuthSensorLocation = new PointF( - (float) faceAuthLocation[0], - (float) faceAuthLocation[1]); + mFaceSensorLocationDefault = new Point( + faceAuthLocation[0], + faceAuthLocation[1]); } - updateFingerprintLocation(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - - context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); - mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); - } - - private int getDisplayWidth() { - DisplayInfo displayInfo = new DisplayInfo(); - mContext.getDisplay().getDisplayInfo(displayInfo); - return displayInfo.getNaturalWidth(); - } - - private void updateFingerprintLocation() { - int xLocation = getDisplayWidth() / 2; + mDisplay = mContext.getDisplay(); + mDisplay.getDisplayInfo(mCachedDisplayInfo); + int xFpLocation = mCachedDisplayInfo.getNaturalWidth() / 2; try { - xLocation = mContext.getResources().getDimensionPixelSize( + xFpLocation = mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen .physical_fingerprint_sensor_center_screen_location_x); } catch (Resources.NotFoundException e) { } - int yLocation = mContext.getResources().getDimensionPixelSize( - com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y); - mFingerprintLocation = new PointF( - xLocation, - yLocation); + mFingerprintSensorLocationDefault = new Point( + xFpLocation, + mContext.getResources().getDimensionPixelSize(com.android.systemui.R.dimen + .physical_fingerprint_sensor_center_screen_location_y) + ); + updateSensorLocations(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @@ -681,19 +752,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba // updateFingerprintLocation in such a case are unclear. private void updateUdfpsLocation() { if (mUdfpsController != null) { - final DisplayInfo displayInfo = new DisplayInfo(); - mContext.getDisplay().getDisplayInfo(displayInfo); - final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio( - mStableDisplaySize.x, mStableDisplaySize.y, displayInfo.getNaturalWidth(), - displayInfo.getNaturalHeight()); - final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0); final Rect previousUdfpsBounds = mUdfpsBounds; mUdfpsBounds = udfpsProp.getLocation().getRect(); - mUdfpsBounds.scale(scaleFactor); + mUdfpsBounds.scale(mScaleFactor); mUdfpsController.updateOverlayParams(udfpsProp.sensorId, - new UdfpsOverlayParams(mUdfpsBounds, displayInfo.getNaturalWidth(), - displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation)); + new UdfpsOverlayParams(mUdfpsBounds, mCachedDisplayInfo.getNaturalWidth(), + mCachedDisplayInfo.getNaturalHeight(), mScaleFactor, + mCachedDisplayInfo.rotation)); if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) { for (Callback cb : mCallbacks) { cb.onUdfpsLocationChanged(); @@ -733,18 +799,23 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mStableDisplaySize = mDisplayManager.getStableDisplaySize(); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + mOrientationListener.enable(); + updateSensorLocations(); } @Override public void setBiometicContextListener(IBiometricContextListener listener) { mBiometricContextListener = listener; - notifyDozeChanged(mStatusBarStateController.isDozing()); + notifyDozeChanged(mStatusBarStateController.isDozing(), + mWakefulnessLifecycle.getWakefulness()); } - private void notifyDozeChanged(boolean isDozing) { + private void notifyDozeChanged(boolean isDozing, + @WakefulnessLifecycle.Wakefulness int wakefullness) { if (mBiometricContextListener != null) { try { - mBiometricContextListener.onDozeChanged(isDozing); + final boolean isAwake = wakefullness == WAKEFULNESS_AWAKE; + mBiometricContextListener.onDozeChanged(isDozing, isAwake); } catch (RemoteException e) { Log.w(TAG, "failed to notify initial doze state"); } @@ -933,7 +1004,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba // BiometricService will have already sent the callback to the client in this case. // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done. mCurrentDialog = null; - mOrientationListener.disable(); } /** @@ -1024,7 +1094,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } mCurrentDialog = newDialog; mCurrentDialog.show(mWindowManager, savedState); - mOrientationListener.enable(); if (!promptInfo.isAllowBackgroundAuthentication()) { mHandler.post(this::cancelIfOwnerIsNotInForeground); @@ -1043,14 +1112,12 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mReceiver = null; mCurrentDialog = null; - mOrientationListener.disable(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - updateFingerprintLocation(); - updateUdfpsLocation(); + updateSensorLocations(); // Save the state of the current dialog (buttons showing, etc) if (mCurrentDialog != null) { @@ -1058,7 +1125,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mCurrentDialog.onSaveState(savedState); mCurrentDialog.dismissWithoutCallback(false /* animate */); mCurrentDialog = null; - mOrientationListener.disable(); // Only show the dialog if necessary. If it was animating out, the dialog is supposed // to send its pending callback immediately. @@ -1079,8 +1145,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } private void onOrientationChanged() { - updateFingerprintLocation(); - updateUdfpsLocation(); + updateSensorLocations(); if (mCurrentDialog != null) { mCurrentDialog.onOrientationChanged(); } @@ -1090,6 +1155,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig, + @NonNull WakefulnessLifecycle wakefulnessLifecycle, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils) { @@ -1103,9 +1169,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba .setOperationId(operationId) .setRequestId(requestId) .setMultiSensorConfig(multiSensorConfig) - .setScaleFactorProvider(() -> { - return getScaleFactor(); - }) + .setScaleFactorProvider(() -> getScaleFactor()) .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, userManager, lockPatternUtils, mInteractionJankMonitor); } @@ -1114,8 +1178,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { final AuthDialog dialog = mCurrentDialog; pw.println(" stableDisplaySize=" + mStableDisplaySize); - pw.println(" faceAuthSensorLocation=" + mFaceAuthSensorLocation); - pw.println(" fingerprintLocation=" + mFingerprintLocation); + pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo); + pw.println(" mScaleFactor=" + mScaleFactor); + pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault); + pw.println(" faceAuthSensorLocation=" + getFaceSensorLocation()); + pw.println(" fingerprintSensorLocationDefault=" + mFingerprintSensorLocationDefault); + pw.println(" fingerprintSensorLocationInNaturalOrientation=" + + getFingerprintSensorLocationInNaturalOrientation()); + pw.println(" fingerprintSensorLocation=" + getFingerprintSensorLocation()); pw.println(" udfpsBounds=" + mUdfpsBounds); pw.println(" allFingerprintAuthenticatorsRegistered=" + mAllFingerprintAuthenticatorsRegistered); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index 51f39b358659..cd0fc3737594 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -48,6 +48,8 @@ public interface AuthDialog extends Dumpable { String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type"; String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props"; + String KEY_BIOMETRIC_ORIENTATION_CHANGED = "orientation_changed"; + int SIZE_UNKNOWN = 0; /** * Minimal UI, showing only biometric icon. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index fd3f6007d8a9..35d96c912b01 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -20,7 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context -import android.graphics.PointF +import android.graphics.Point import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricSourceType import android.util.Log @@ -79,8 +79,8 @@ class AuthRippleController @Inject constructor( @VisibleForTesting internal var startLightRevealScrimOnKeyguardFadingAway = false var lightRevealScrimAnimator: ValueAnimator? = null - var fingerprintSensorLocation: PointF? = null - private var faceSensorLocation: PointF? = null + var fingerprintSensorLocation: Point? = null + private var faceSensorLocation: Point? = null private var circleReveal: LightRevealEffect? = null private var udfpsController: UdfpsController? = null @@ -131,10 +131,10 @@ class AuthRippleController @Inject constructor( circleReveal = CircleReveal( it.x, it.y, - 0f, + 0, Math.max( - Math.max(it.x, centralSurfaces.displayWidth - it.x), - Math.max(it.y, centralSurfaces.displayHeight - it.y) + Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x), + Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y) ) ) showUnlockedRipple() @@ -148,10 +148,10 @@ class AuthRippleController @Inject constructor( circleReveal = CircleReveal( it.x, it.y, - 0f, + 0, Math.max( - Math.max(it.x, centralSurfaces.displayWidth - it.x), - Math.max(it.y, centralSurfaces.displayHeight - it.y) + Math.max(it.x, centralSurfaces.displayWidth.toInt() - it.x), + Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y) ) ) showUnlockedRipple() @@ -228,7 +228,7 @@ class AuthRippleController @Inject constructor( fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation - faceSensorLocation = authController.faceAuthSensorLocation + faceSensorLocation = authController.faceSensorLocation } private fun updateRippleColor() { @@ -362,9 +362,8 @@ class AuthRippleController @Inject constructor( invalidCommand(pw) return } - pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " + - args[2].toFloat()) - mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat())) + pw.println("custom ripple sensorLocation=" + args[1] + ", " + args[2]) + mView.setSensorLocation(Point(args[1].toInt(), args[2].toInt())) showUnlockedRipple() } else -> invalidCommand(pw) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 1c574808e0e9..c93fe6ac9f34 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -23,7 +23,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint -import android.graphics.PointF +import android.graphics.Point import android.util.AttributeSet import android.view.View import android.view.animation.PathInterpolator @@ -68,7 +68,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at dwellShader.maxRadius = value field = value } - private var dwellOrigin: PointF = PointF() + private var dwellOrigin: Point = Point() set(value) { dwellShader.origin = value field = value @@ -78,9 +78,9 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at rippleShader.setMaxSize(value * 2f, value * 2f) field = value } - private var origin: PointF = PointF() + private var origin: Point = Point() set(value) { - rippleShader.setCenter(value.x, value.y) + rippleShader.setCenter(value.x.toFloat(), value.y.toFloat()) field = value } @@ -97,12 +97,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at visibility = GONE } - fun setSensorLocation(location: PointF) { + fun setSensorLocation(location: Point) { origin = location radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() } - fun setFingerprintSensorLocation(location: PointF, sensorRadius: Float) { + fun setFingerprintSensorLocation(location: Point, sensorRadius: Float) { origin = location radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() dwellOrigin = location @@ -349,13 +349,15 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at if (drawDwell) { val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) * (1 - dwellShader.progress)) * dwellRadius * 2f - canvas?.drawCircle(dwellOrigin.x, dwellOrigin.y, maskRadius, dwellPaint) + canvas?.drawCircle(dwellOrigin.x.toFloat(), dwellOrigin.y.toFloat(), + maskRadius, dwellPaint) } if (drawRipple) { val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * (1 - rippleShader.progress)) * radius * 2f - canvas?.drawCircle(origin.x, origin.y, mask, ripplePaint) + canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(), + mask, ripplePaint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt index 979fe33fb25b..e5c4fb42ffe8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt @@ -16,7 +16,7 @@ package com.android.systemui.biometrics -import android.graphics.PointF +import android.graphics.Point import android.graphics.RuntimeShader import android.util.MathUtils @@ -94,10 +94,10 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER) { /** * Origin coordinate of the ripple. */ - var origin: PointF = PointF() + var origin: Point = Point() set(value) { field = value - setFloatUniform("in_origin", value.x, value.y) + setFloatUniform("in_origin", value.x.toFloat(), value.y.toFloat()) } /** @@ -107,7 +107,7 @@ class DwellRippleShader internal constructor() : RuntimeShader(SHADER) { set(value) { field = value setFloatUniform("in_radius", - (1 - (1 - value) * (1 - value) * (1 - value))* maxRadius) + (1 - (1 - value) * (1 - value) * (1 - value)) * maxRadius) setFloatUniform("in_blur", MathUtils.lerp(1f, 0.7f, value)) } diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index 4fe2dd810a08..e2ef2477c836 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -33,7 +33,6 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.shared.system.ActivityManagerKt.isInForeground import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.CentralSurfaces @@ -117,7 +116,7 @@ class CameraGestureHelper @Inject constructor( ) } catch (e: RemoteException) { Log.w( - NotificationPanelViewController.TAG, + "CameraGestureHelper", "Unable to start camera activity", e ) diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index da675de2b9ec..dec3d6b679c2 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt @@ -200,8 +200,7 @@ class WiredChargingRippleController @Inject constructor( } private fun updateRippleColor() { - rippleView.setColor( - Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor) + rippleView.setColor(Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor) } inner class ChargingRippleCommand : Command { diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 08393386c27f..c0cc6b41aafd 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -30,13 +30,12 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import androidx.core.graphics.ColorUtils; - import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.ripple.RippleView; +import com.android.systemui.ripple.RippleViewKt; import java.text.NumberFormat; @@ -143,16 +142,15 @@ final class WirelessChargingLayout extends FrameLayout { mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); + int color = Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor(); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); mRippleView.setSparkleStrength(0.22f); - int color = Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor(); - mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28)); + mRippleView.setColor(color, 28); } else { mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); - mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor()); + mRippleView.setColor(color, RippleViewKt.RIPPLE_DEFAULT_ALPHA); } OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt new file mode 100644 index 000000000000..cbed21cf65d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.content.res.Resources +import android.util.Log +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayInfo + +class CutoutDecorProviderFactory constructor( + private val res: Resources, + private val display: Display?, +) : DecorProviderFactory() { + + val displayInfo = DisplayInfo() + + override val hasProviders: Boolean + get() { + display?.getDisplayInfo(displayInfo) ?: run { + Log.w(TAG, "display is null, can't update displayInfo") + } + return DisplayCutout.getFillBuiltInDisplayCutout(res, displayInfo.uniqueId) + } + + override val providers: List<DecorProvider> + get() { + if (!hasProviders) { + return emptyList() + } + + return ArrayList<DecorProvider>().also { list -> + // We need to update displayInfo before using it, but it has already updated during + // accessing hasProviders field + displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds -> + for (bound in bounds) { + list.add( + CutoutDecorProviderImpl(bound.baseOnRotation0(displayInfo.rotation)) + ) + } + } + } + } +} + +private const val TAG = "CutoutDecorProviderFactory" diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt new file mode 100644 index 000000000000..991b54e8035e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.content.Context +import android.view.DisplayCutout +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.ScreenDecorations.DisplayCutoutView + +class CutoutDecorProviderImpl( + @DisplayCutout.BoundsPosition override val alignedBound: Int +) : BoundDecorProvider() { + + override val viewId: Int = when (alignedBound) { + DisplayCutout.BOUNDS_POSITION_TOP -> R.id.display_cutout + DisplayCutout.BOUNDS_POSITION_LEFT -> R.id.display_cutout_left + DisplayCutout.BOUNDS_POSITION_RIGHT -> R.id.display_cutout_right + else -> R.id.display_cutout_bottom + } + + override fun inflateView( + context: Context, + parent: ViewGroup, + @Surface.Rotation rotation: Int, + tintColor: Int + ): View { + return DisplayCutoutView(context, alignedBound).also { view -> + view.id = viewId + view.setColor(tintColor) + parent.addView(view) + view.updateRotation(rotation) + } + } + + override fun onReloadResAndMeasure( + view: View, + reloadToken: Int, + @Surface.Rotation rotation: Int, + tintColor: Int, + displayUniqueId: String? + ) { + (view as? DisplayCutoutView)?.let { cutoutView -> + cutoutView.setColor(tintColor) + cutoutView.updateRotation(rotation) + cutoutView.onDisplayChanged(displayUniqueId) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt index de6d7278971c..0681f50726a8 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt @@ -32,7 +32,7 @@ abstract class DecorProvider { abstract val viewId: Int /** The number of total aligned bounds */ - val numOfAlignedEdge: Int + val numOfAlignedBound: Int get() = alignedBounds.size /** The aligned bounds for the view which is created through inflateView() */ @@ -57,16 +57,8 @@ abstract class DecorProvider { @Surface.Rotation rotation: Int, tintColor: Int ): View -} -/** - * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this - * alignedBound element. The providers on the second list do not contain this alignedBound element - */ -fun List<DecorProvider>.partitionAlignedBound( - @DisplayCutout.BoundsPosition alignedBound: Int -): Pair<List<DecorProvider>, List<DecorProvider>> { - return partition { it.alignedBounds.contains(alignedBound) } + override fun toString() = "${javaClass.simpleName}{alignedBounds=$alignedBounds}" } /** @@ -94,3 +86,61 @@ abstract class BoundDecorProvider : DecorProvider() { listOf(alignedBound) } } + +/** + * Split list to 2 sub-lists, and return it back as Pair<>. The providers on the first list contains + * this alignedBound element. The providers on the second list do not contain this alignedBound + * element. + */ +fun List<DecorProvider>.partitionAlignedBound( + @DisplayCutout.BoundsPosition alignedBound: Int +): Pair<List<DecorProvider>, List<DecorProvider>> { + return partition { it.alignedBounds.contains(alignedBound) } +} + +/** + * Get the proper bound from DecorProvider list + * Time complexity: O(N), N is the number of providers + * + * Choose order + * 1. Return null if list is empty + * 2. If list contains BoundDecorProvider, return its alignedBound[0] because it is a must-have + * bound + * 3. Return the bound with most DecorProviders + */ +fun List<DecorProvider>.getProperBound(): Int? { + // Return null if list is empty + if (isEmpty()) { + return null + } + + // Choose alignedBounds[0] of BoundDecorProvider if any + val singleBoundProvider = firstOrNull { it.numOfAlignedBound == 1 } + if (singleBoundProvider != null) { + return singleBoundProvider.alignedBounds[0] + } + + // Return the bound with most DecorProviders + val boundCount = intArrayOf(0, 0, 0, 0) + for (provider in this) { + for (bound in provider.alignedBounds) { + boundCount[bound]++ + } + } + var maxCount = 0 + var maxCountBound: Int? = null + val bounds = arrayOf( + // Put top and bottom at first to get the highest priority to be chosen + DisplayCutout.BOUNDS_POSITION_TOP, + DisplayCutout.BOUNDS_POSITION_BOTTOM, + DisplayCutout.BOUNDS_POSITION_LEFT, + DisplayCutout.BOUNDS_POSITION_RIGHT + ) + for (bound in bounds) { + if (boundCount[bound] > maxCount) { + maxCountBound = bound + maxCount = boundCount[bound] + } + } + return maxCountBound +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 5925c57b4518..ec0013bb5000 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -32,8 +32,8 @@ import android.widget.FrameLayout import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.FaceScanningOverlay import com.android.systemui.biometrics.AuthController -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -55,7 +55,7 @@ class FaceScanningProviderFactory @Inject constructor( override val hasProviders: Boolean get() { if (!featureFlags.isEnabled(Flags.FACE_SCANNING_ANIM) || - authController.faceAuthSensorLocation == null) { + authController.faceSensorLocation == null) { return false } @@ -159,7 +159,7 @@ class FaceScanningOverlayProviderImpl( layoutParams.let { lp -> lp.width = ViewGroup.LayoutParams.MATCH_PARENT lp.height = ViewGroup.LayoutParams.MATCH_PARENT - authController.faceAuthSensorLocation?.y?.let { faceAuthSensorHeight -> + authController.faceSensorLocation?.y?.let { faceAuthSensorHeight -> val faceScanningHeight = (faceAuthSensorHeight * 2).toInt() when (rotation) { Surface.ROTATION_0, Surface.ROTATION_180 -> diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt index dfb0b5aad912..45b8a08a9b45 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -114,7 +114,8 @@ class OverlayWindow(private val context: Context) { pw.println(" rootView=$rootView") for (i in 0 until rootView.childCount) { val child = rootView.getChildAt(i) - pw.println(" child[$i]=$child") + val provider = viewProviderMap[child.id]?.second + pw.println(" child[$i]=$child $provider") } } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java index d5db63dc9093..75a97de10e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java @@ -35,7 +35,7 @@ import java.util.Set; public class ComplicationUtils { /** * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to - * {@link ComplicationType}. + * {@link Complication.ComplicationType}. */ @Complication.ComplicationType public static int convertComplicationType(@DreamBackend.ComplicationType int type) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 1c72e49eb06a..2503d3c3edad 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -151,8 +151,8 @@ public class DreamHomeControlsComplication implements Complication { * Controls behavior of the dream complication. */ static class DreamHomeControlsChipViewController extends ViewController<ImageView> { - private static final boolean DEBUG = false; private static final String TAG = "DreamHomeControlsCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ActivityStarter mActivityStarter; private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java new file mode 100644 index 000000000000..21a51d1096d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW; +import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS; + +import android.util.Log; +import android.view.View; + +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent; +import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * A dream complication that shows a media entry chip to launch media control view. + */ +public class DreamMediaEntryComplication implements Complication { + private final DreamMediaEntryComplicationComponent.Factory mComponentFactory; + + @Inject + public DreamMediaEntryComplication( + DreamMediaEntryComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } + + /** + * Contains values/logic associated with the dream complication view. + */ + public static class DreamMediaEntryViewHolder implements ViewHolder { + private final View mView; + private final ComplicationLayoutParams mLayoutParams; + private final DreamMediaEntryViewController mViewController; + + @Inject + DreamMediaEntryViewHolder( + DreamMediaEntryViewController dreamMediaEntryViewController, + @Named(DREAM_MEDIA_ENTRY_VIEW) View view, + @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams + ) { + mView = view; + mLayoutParams = layoutParams; + mViewController = dreamMediaEntryViewController; + mViewController.init(); + } + + @Override + public View getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * Controls behavior of the dream complication. + */ + static class DreamMediaEntryViewController extends ViewController<View> { + private static final String TAG = "DreamMediaEntryVwCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final DreamOverlayStateController mDreamOverlayStateController; + private final MediaDreamComplication mMediaComplication; + + private boolean mMediaComplicationAdded; + + @Inject + DreamMediaEntryViewController( + @Named(DREAM_MEDIA_ENTRY_VIEW) View view, + DreamOverlayStateController dreamOverlayStateController, + MediaDreamComplication mediaComplication) { + super(view); + mDreamOverlayStateController = dreamOverlayStateController; + mMediaComplication = mediaComplication; + mView.setOnClickListener(this::onClickMediaEntry); + } + + @Override + protected void onViewAttached() { + } + + @Override + protected void onViewDetached() { + removeMediaComplication(); + } + + private void onClickMediaEntry(View v) { + if (DEBUG) Log.d(TAG, "media entry complication tapped"); + + if (!mMediaComplicationAdded) { + addMediaComplication(); + } else { + removeMediaComplication(); + } + } + + private void addMediaComplication() { + mView.setSelected(true); + mDreamOverlayStateController.addComplication(mMediaComplication); + mMediaComplicationAdded = true; + } + + private void removeMediaComplication() { + mView.setSelected(false); + mDreamOverlayStateController.removeComplication(mMediaComplication); + mMediaComplicationAdded = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index 567bdbc01170..a981f255a873 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -70,11 +70,7 @@ public class SmartSpaceComplication implements Complication { new BcSmartspaceDataPlugin.SmartspaceTargetListener() { @Override public void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets) { - if (!targets.isEmpty()) { - mDreamOverlayStateController.addComplication(mComplication); - } else { - mDreamOverlayStateController.removeComplication(mComplication); - } + mDreamOverlayStateController.addComplication(mComplication); } }; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java new file mode 100644 index 000000000000..ed05daf35ed9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.LayoutInflater; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Responsible for generating dependencies for the {@link DreamMediaEntryComplication}. + */ +@Subcomponent(modules = DreamMediaEntryComplicationComponent.DreamMediaEntryModule.class) +@DreamMediaEntryComplicationComponent.DreamMediaEntryComplicationScope +public interface DreamMediaEntryComplicationComponent { + /** + * Creates a view holder for the media entry complication. + */ + DreamMediaEntryComplication.DreamMediaEntryViewHolder getViewHolder(); + + /** + * Scope of the media entry complication. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamMediaEntryComplicationScope {} + + /** + * Factory that generates a {@link DreamMediaEntryComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + DreamMediaEntryComplicationComponent create(); + } + + /** + * Scoped injected values for the {@link DreamMediaEntryComplicationComponent}. + */ + @Module + interface DreamMediaEntryModule { + String DREAM_MEDIA_ENTRY_VIEW = "dream_media_entry_view"; + + /** + * Provides the dream media entry view. + */ + @Provides + @DreamMediaEntryComplicationScope + @Named(DREAM_MEDIA_ENTRY_VIEW) + static View provideMediaEntryView(LayoutInflater layoutInflater) { + return (View) layoutInflater.inflate(R.layout.dream_overlay_media_entry_chip, null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index eb07238ce752..759d6ec3bf21 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -38,15 +38,19 @@ import dagger.Provides; }, subcomponents = { DreamHomeControlsComplicationComponent.class, + DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params"; String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; + String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0; + int DREAM_MEDIA_COMPLICATION_WEIGHT = -1; int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1; + int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0; /** * Provides layout parameters for the clock time complication. @@ -78,6 +82,21 @@ public interface RegisteredComplicationsModule { } /** + * Provides layout parameters for the media entry complication. + */ + @Provides + @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS) + static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) { + return new ComplicationLayoutParams( + res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), + res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT); + } + + /** * Provides layout parameters for the smartspace complication. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 9e2b7c748631..a3dc77993d30 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -50,7 +50,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.CoreStartable; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; @@ -61,6 +60,7 @@ import java.util.List; import java.util.Set; import javax.inject.Inject; +import javax.inject.Provider; /** */ @SysUISingleton @@ -106,6 +106,8 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo protected volatile Context mContext; + private final Provider<LocalBluetoothManager> mBluetoothManagerProvider; + private boolean mEnabled; private String mKeyboardName; private CachedBluetoothDeviceManager mCachedDeviceManager; @@ -122,8 +124,9 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private int mState; @Inject - public KeyboardUI(Context context) { + public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) { super(context); + this.mBluetoothManagerProvider = bluetoothManagerProvider; } @Override @@ -181,7 +184,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo return; } - LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class); + LocalBluetoothManager bluetoothManager = mBluetoothManagerProvider.get(); if (bluetoothManager == null) { if (DEBUG) { Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance"); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 08cf57c7594e..b39770d302be 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -356,15 +356,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mHeaderSubtitle.setText(subTitle); mHeaderTitle.setGravity(Gravity.NO_GRAVITY); } - if (!mAdapter.isDragging()) { - int currentActivePosition = mAdapter.getCurrentActivePosition(); - if (!colorSetUpdated && !deviceSetChanged && currentActivePosition >= 0 - && currentActivePosition < mAdapter.getItemCount()) { - mAdapter.notifyItemChanged(currentActivePosition); - } else { - mAdapter.notifyDataSetChanged(); - } - } // Show when remote media session is available or // when the device supports BT LE audio + media is playing mStopButton.setVisibility(getStopButtonVisibility()); @@ -374,6 +365,18 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mBroadcastIcon.setVisibility(getBroadcastIconVisibility()); mBroadcastIcon.setOnClickListener(v -> onBroadcastIconClick()); + if (!mAdapter.isDragging()) { + int currentActivePosition = mAdapter.getCurrentActivePosition(); + if (!colorSetUpdated && !deviceSetChanged && currentActivePosition >= 0 + && currentActivePosition < mAdapter.getItemCount()) { + mAdapter.notifyItemChanged(currentActivePosition); + } else { + mAdapter.notifyDataSetChanged(); + } + } else { + mMediaOutputController.setRefreshing(false); + mMediaOutputController.refreshDataSetIfNeeded(); + } } private void updateButtonBackgroundColorFilter() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index c5448713970c..acd04f216a98 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -25,6 +25,7 @@ import androidx.annotation.Nullable; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; @@ -54,7 +55,7 @@ public class MediaDreamSentinel extends CoreStartable { } mAdded = false; - mDreamOverlayStateController.removeComplication(mComplication); + mDreamOverlayStateController.removeComplication(mMediaEntryComplication); } @Override @@ -79,24 +80,24 @@ public class MediaDreamSentinel extends CoreStartable { } mAdded = true; - mDreamOverlayStateController.addComplication(mComplication); + mDreamOverlayStateController.addComplication(mMediaEntryComplication); } }; private final MediaDataManager mMediaDataManager; private final DreamOverlayStateController mDreamOverlayStateController; - private final MediaDreamComplication mComplication; + private final DreamMediaEntryComplication mMediaEntryComplication; private final FeatureFlags mFeatureFlags; @Inject public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication complication, + DreamMediaEntryComplication mediaEntryComplication, FeatureFlags featureFlags) { super(context); mMediaDataManager = mediaDataManager; mDreamOverlayStateController = dreamOverlayStateController; - mComplication = complication; + mMediaEntryComplication = mediaEntryComplication; mFeatureFlags = featureFlags; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java index 3408d97dd33f..052608f17e0e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dream.dagger; +import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_COMPLICATION_WEIGHT; + import static java.lang.annotation.RetentionPolicy.RUNTIME; import android.content.Context; @@ -93,7 +95,7 @@ public interface MediaComplicationComponent { ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START, ComplicationLayoutParams.DIRECTION_DOWN, - 0, + DREAM_MEDIA_COMPLICATION_WEIGHT, true); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 00a22f20e94d..35a6c74518e0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -30,7 +30,6 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import androidx.core.graphics.ColorUtils import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton @@ -209,8 +208,7 @@ class MediaTttChipControllerReceiver @Inject constructor( // Center the ripple on the bottom of the screen in the middle. rippleView.setCenter(width * 0.5f, height.toFloat()) val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) - val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70) - rippleView.setColor(colorWithAlpha) + rippleView.setColor(color, 70) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 918c6be582e7..8f8590516faa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -27,7 +27,6 @@ import android.view.View; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; @@ -61,8 +60,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final BrightnessMirrorHandler mBrightnessMirrorHandler; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private boolean mGridContentVisible = true; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @Override @@ -204,16 +201,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { }); } - /** */ - public void setGridContentVisibility(boolean visible) { - int newVis = visible ? View.VISIBLE : View.INVISIBLE; - setVisibility(newVis); - if (mGridContentVisible != visible) { - mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis); - } - mGridContentVisible = visible; - } - public boolean isLayoutRtl() { return mView.isLayoutRtl(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index ce6aaae6e98c..0ec4eef1e551 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -43,6 +43,7 @@ import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.user.data.source.UserRecord; import javax.inject.Inject; @@ -95,7 +96,7 @@ public class UserDetailView extends PseudoGridView { @Override public View getView(int position, View convertView, ViewGroup parent) { - UserSwitcherController.UserRecord item = getItem(position); + UserRecord item = getItem(position); return createUserDetailItemView(convertView, parent, item); } @@ -113,7 +114,7 @@ public class UserDetailView extends PseudoGridView { } public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, - UserSwitcherController.UserRecord item) { + UserRecord item) { UserDetailItemView v = UserDetailItemView.convertOrInflate( parent.getContext(), convertView, parent); if (!item.isCurrent || item.isGuest) { @@ -134,7 +135,7 @@ public class UserDetailView extends PseudoGridView { v.bind(name, drawable, item.info.id); } v.setActivated(item.isCurrent); - v.setDisabledByAdmin(item.isDisabledByAdmin); + v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); @@ -146,7 +147,7 @@ public class UserDetailView extends PseudoGridView { } private static Drawable getDrawable(Context context, - UserSwitcherController.UserRecord item) { + UserRecord item) { Drawable icon = getIconDrawable(context, item); int iconColorRes; if (item.isCurrent) { @@ -171,22 +172,24 @@ public class UserDetailView extends PseudoGridView { } Trace.beginSection("UserDetailView.Adapter#onClick"); - UserSwitcherController.UserRecord tag = - (UserSwitcherController.UserRecord) view.getTag(); - if (tag.isDisabledByAdmin) { + UserRecord userRecord = + (UserRecord) view.getTag(); + if (mController.isDisabledByAdmin(userRecord)) { final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( - mContext, tag.enforcedAdmin); + mContext, mController.getEnforcedAdmin(userRecord)); mController.startActivity(intent); - } else if (tag.isSwitchToEnabled) { + } else if (userRecord.isSwitchToEnabled) { MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER); mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH); - if (!tag.isAddUser && !tag.isRestricted && !tag.isDisabledByAdmin) { + if (!userRecord.isAddUser + && !userRecord.isRestricted + && !mController.isDisabledByAdmin(userRecord)) { if (mCurrentUserView != null) { mCurrentUserView.setActivated(false); } view.setActivated(true); } - onUserListItemClicked(tag, mDialogShower); + onUserListItemClicked(userRecord, mDialogShower); } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index db7c1fd86be0..d2f3a6a7bee1 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -68,7 +68,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ @@ -84,7 +84,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ @@ -100,7 +100,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt index 60c8f3719a2e..1e51ffa292b7 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt @@ -25,10 +25,12 @@ import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet import android.view.View +import androidx.core.graphics.ColorUtils import com.android.systemui.ripple.RippleShader.RippleShape private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() +const val RIPPLE_DEFAULT_ALPHA: Int = 45 /** * A generic expanding ripple effect. @@ -111,9 +113,12 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a rippleInProgress = true } - /** Set the color to be used for the ripple. */ - fun setColor(color: Int) { - rippleShader.color = color + /** Set the color to be used for the ripple. + * + * The alpha value of the color will be applied to the ripple. The alpha range is [0-100]. + */ + fun setColor(color: Int, alpha: Int = RIPPLE_DEFAULT_ALPHA) { + rippleShader.color = ColorUtils.setAlphaComponent(color, alpha) } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 35f32caffe21..695a80b2b95d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -229,6 +229,7 @@ public class TakeScreenshotService extends Service { Log.d(TAG, "handleMessage: Using request processor"); mProcessor.processAsync(request, (r) -> dispatchToController(r, onSaved, callback)); + return; } dispatchToController(request, onSaved, callback); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java index 9818af3751a7..1cdacb93ba45 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java @@ -17,32 +17,30 @@ package com.android.systemui.shade; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.statusbar.phone.TapAgainView; -public class NotificationPanelView extends PanelView { +/** The shade view. */ +public final class NotificationPanelView extends FrameLayout { + static final boolean DEBUG = false; - private static final boolean DEBUG = false; - - /** - * Fling expanding QS. - */ - public static final int FLING_EXPAND = 0; - - public static final String COUNTER_PANEL_OPEN = "panel_open"; - public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; + private final Paint mAlphaPaint = new Paint(); private int mCurrentPanelAlpha; - private final Paint mAlphaPaint = new Paint(); private boolean mDozing; private RtlChangeListener mRtlChangeListener; + private NotificationPanelViewController.TouchHandler mTouchHandler; + private OnConfigurationChangedListener mOnConfigurationChangedListener; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -99,7 +97,36 @@ public class NotificationPanelView extends PanelView { return findViewById(R.id.shade_falsing_tap_again); } + /** Sets the touch handler for this view. */ + public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) { + super.setOnTouchListener(touchHandler); + mTouchHandler = touchHandler; + } + + void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) { + mOnConfigurationChangedListener = listener; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return mTouchHandler.onInterceptTouchEvent(event); + } + + @Override + public void dispatchConfigurationChanged(Configuration newConfig) { + super.dispatchConfigurationChanged(newConfig); + mOnConfigurationChangedListener.onConfigurationChanged(newConfig); + } + + /** Callback for right-to-left setting changes. */ interface RtlChangeListener { + /** Called when right-to-left setting changes. */ void onRtlPropertielsChanged(int layoutDirection); } + + /** Callback for config changes. */ + interface OnConfigurationChangedListener { + /** Called when configuration changes. */ + void onConfigurationChanged(Configuration newConfig); + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 5f07f9f1c9ad..70c0cab43e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -17,8 +17,6 @@ package com.android.systemui.shade; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; @@ -28,12 +26,8 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; -import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; -import static com.android.systemui.classifier.Classifier.GENERIC; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; -import static com.android.systemui.classifier.Classifier.UNLOCK; -import static com.android.systemui.shade.PanelView.DEBUG; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -46,8 +40,6 @@ import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStat import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING; import static com.android.systemui.util.DumpUtilsKt.asIndenting; -import static java.lang.Float.isNaN; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -55,8 +47,6 @@ import android.annotation.NonNull; import android.app.Fragment; import android.app.StatusBarManager; import android.content.ContentResolver; -import android.content.res.Configuration; -import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; @@ -81,13 +71,11 @@ import android.transition.TransitionManager; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; -import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.View.AccessibilityDelegate; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.view.ViewStub; @@ -96,7 +84,6 @@ import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.Interpolator; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -191,7 +178,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.BounceInterpolator; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -247,25 +233,12 @@ import javax.inject.Inject; import javax.inject.Provider; @CentralSurfacesComponent.CentralSurfacesScope -public final class NotificationPanelViewController { - public static final String TAG = PanelView.class.getSimpleName(); - public static final float FLING_MAX_LENGTH_SECONDS = 0.6f; - public static final float FLING_SPEED_UP_FACTOR = 0.6f; - public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f; - public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f; - private static final int NO_FIXED_DURATION = -1; - private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; - private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; - - /** - * The factor of the usual high velocity that is needed in order to reach the maximum overshoot - * when flinging. A low value will make it that most flings will reach the maximum overshoot. - */ - private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; +public final class NotificationPanelViewController extends PanelViewController { private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; + /** * The parallax amount of the quick settings translation when dragging down the panel */ @@ -274,7 +247,7 @@ public final class NotificationPanelViewController { /** * Fling expanding QS. */ - private static final int FLING_EXPAND = 0; + public static final int FLING_EXPAND = 0; /** * Fling collapsing QS, potentially stopping when QS becomes QQS. @@ -290,16 +263,6 @@ public final class NotificationPanelViewController { - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; - private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; - private final Resources mResources; - private final KeyguardStateController mKeyguardStateController; - private final SysuiStatusBarStateController mStatusBarStateController; - private final AmbientState mAmbientState; - private final LockscreenGestureLogger mLockscreenGestureLogger; - private final SystemClock mSystemClock; - - private final ShadeLogger mShadeLog; - private final DozeParameters mDozeParameters; private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener(); private final Runnable mCollapseExpandAction = new CollapseExpandAction(); @@ -373,28 +336,6 @@ public final class NotificationPanelViewController { private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; private final RecordingController mRecordingController; private final PanelEventsEmitter mPanelEventsEmitter; - private final boolean mVibrateOnOpening; - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - private final FlingAnimationUtils mFlingAnimationUtilsClosing; - private final FlingAnimationUtils mFlingAnimationUtilsDismissing; - private final LatencyTracker mLatencyTracker; - private final DozeLog mDozeLog; - /** Whether or not the PanelView can be expanded or collapsed with a drag. */ - private final boolean mNotificationsDragEnabled; - private final Interpolator mBounceInterpolator; - private final NotificationShadeWindowController mNotificationShadeWindowController; - private final PanelExpansionStateManager mPanelExpansionStateManager; - private long mDownTime; - private boolean mTouchSlopExceededBeforeDown; - private boolean mIsLaunchAnimationRunning; - private float mOverExpansion; - private CentralSurfaces mCentralSurfaces; - private HeadsUpManagerPhone mHeadsUpManager; - private float mExpandedHeight = 0; - private boolean mTracking; - private boolean mHintAnimationRunning; - private KeyguardBottomAreaView mKeyguardBottomArea; - private boolean mExpanding; private boolean mSplitShadeEnabled; /** The bottom padding reserved for elements of the keyguard measuring notifications. */ private float mKeyguardNotificationBottomPadding; @@ -430,7 +371,8 @@ public final class NotificationPanelViewController { private boolean mQsTracking; /** - * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and + * If set, the ongoing touch gesture might both trigger the expansion in {@link + * NotificationPanelView} and * the expansion for quick settings. */ private boolean mConflictingQsExpansionGesture; @@ -757,51 +699,6 @@ public final class NotificationPanelViewController { private final CameraGestureHelper mCameraGestureHelper; private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider; private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider; - private float mMinExpandHeight; - private boolean mPanelUpdateWhenAnimatorEnds; - private int mFixedDuration = NO_FIXED_DURATION; - /** The overshoot amount when the panel flings open */ - private float mPanelFlingOvershootAmount; - /** The amount of pixels that we have overexpanded the last time with a gesture */ - private float mLastGesturedOverExpansion = -1; - /** Is the current animator the spring back animation? */ - private boolean mIsSpringBackAnimation; - private boolean mInSplitShade; - private float mHintDistance; - private float mInitialOffsetOnTouch; - private boolean mCollapsedAndHeadsUpOnDown; - private float mExpandedFraction = 0; - private float mExpansionDragDownAmountPx = 0; - private boolean mPanelClosedOnDown; - private boolean mHasLayoutedSinceDown; - private float mUpdateFlingVelocity; - private boolean mUpdateFlingOnLayout; - private boolean mClosing; - private boolean mTouchSlopExceeded; - private int mTrackingPointer; - private int mTouchSlop; - private float mSlopMultiplier; - private boolean mTouchAboveFalsingThreshold; - private boolean mTouchStartedInEmptyArea; - private boolean mMotionAborted; - private boolean mUpwardsWhenThresholdReached; - private boolean mAnimatingOnDown; - private boolean mHandlingPointerUp; - private ValueAnimator mHeightAnimator; - /** Whether instant expand request is currently pending and we are just waiting for layout. */ - private boolean mInstantExpanding; - private boolean mAnimateAfterExpanding; - private boolean mIsFlinging; - private String mViewName; - private float mInitialExpandY; - private float mInitialExpandX; - private boolean mTouchDisabled; - private boolean mInitialTouchFromKeyguard; - /** Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */ - private float mNextCollapseSpeedUpFactor = 1.0f; - private boolean mGestureWaitForTouchSlop; - private boolean mIgnoreXTouchSlop; - private boolean mExpandLatencyTracking; @Inject public NotificationPanelViewController(NotificationPanelView view, @@ -825,7 +722,7 @@ public final class NotificationPanelViewController { MetricsLogger metricsLogger, ShadeLogger shadeLogger, ConfigurationController configurationController, - Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider, + Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder, StatusBarTouchableRegionManager statusBarTouchableRegionManager, ConversationNotificationManager conversationNotificationManager, MediaHierarchyManager mediaHierarchyManager, @@ -875,68 +772,25 @@ public final class NotificationPanelViewController { CameraGestureHelper cameraGestureHelper, Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider, Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) { - keyguardStateController.addCallback(new KeyguardStateController.Callback() { - @Override - public void onKeyguardFadingAwayChanged() { - requestPanelHeightUpdate(); - } - }); - mAmbientState = ambientState; + super(view, + falsingManager, + dozeLog, + keyguardStateController, + (SysuiStatusBarStateController) statusBarStateController, + notificationShadeWindowController, + vibratorHelper, + statusBarKeyguardViewManager, + latencyTracker, + flingAnimationUtilsBuilder.get(), + statusBarTouchableRegionManager, + lockscreenGestureLogger, + panelExpansionStateManager, + ambientState, + interactionJankMonitor, + shadeLogger, + systemClock); mView = view; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; - mLockscreenGestureLogger = lockscreenGestureLogger; - mPanelExpansionStateManager = panelExpansionStateManager; - mShadeLog = shadeLogger; - TouchHandler touchHandler = createTouchHandler(); - mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - mViewName = mResources.getResourceName(mView.getId()); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - - mView.addOnLayoutChangeListener(createLayoutChangeListener()); - mView.setOnTouchListener(touchHandler); - mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); - - mResources = mView.getResources(); - mKeyguardStateController = keyguardStateController; - mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; - mNotificationShadeWindowController = notificationShadeWindowController; - FlingAnimationUtils.Builder flingAnimationUtilsBuilder = - flingAnimationUtilsBuilderProvider.get(); - mFlingAnimationUtils = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) - .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) - .build(); - mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS) - .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR) - .build(); - mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder - .reset() - .setMaxLengthSeconds(0.5f) - .setSpeedUpFactor(0.6f) - .setX2(0.6f) - .setY2(0.84f) - .build(); - mLatencyTracker = latencyTracker; - mBounceInterpolator = new BounceInterpolator(); - mFalsingManager = falsingManager; - mDozeLog = dozeLog; - mNotificationsDragEnabled = mResources.getBoolean( - R.bool.config_enableNotificationShadeDrag); mVibratorHelper = vibratorHelper; - mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); - mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; - mInteractionJankMonitor = interactionJankMonitor; - mSystemClock = systemClock; mKeyguardMediaController = keyguardMediaController; mPrivacyDotViewController = privacyDotViewController; mQuickAccessWalletController = quickAccessWalletController; @@ -944,8 +798,9 @@ public final class NotificationPanelViewController { mControlsComponent = controlsComponent; mMetricsLogger = metricsLogger; mConfigurationController = configurationController; - mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilderProvider; + mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; mMediaHierarchyManager = mediaHierarchyManager; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mNotificationsQSContainerController = notificationsQSContainerController; mNotificationListContainer = notificationListContainer; mNotificationStackSizeCalculator = notificationStackSizeCalculator; @@ -967,6 +822,7 @@ public final class NotificationPanelViewController { mLargeScreenShadeHeaderController = largeScreenShadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; + mFalsingManager = falsingManager; mFalsingCollector = falsingCollector; mPowerManager = powerManager; mWakeUpCoordinator = coordinator; @@ -982,6 +838,7 @@ public final class NotificationPanelViewController { mUserManager = userManager; mMediaDataManager = mediaDataManager; mTapAgainViewController = tapAgainViewController; + mInteractionJankMonitor = interactionJankMonitor; mSysUiState = sysUiState; mPanelEventsEmitter = panelEventsEmitter; pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -1184,14 +1041,9 @@ public final class NotificationPanelViewController { controller.setup(mNotificationContainerParent)); } - @VisibleForTesting - void loadDimens() { - final ViewConfiguration configuration = ViewConfiguration.get(this.mView.getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); - mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); - mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); - mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); - mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); + @Override + protected void loadDimens() { + super.loadDimens(); mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get() .setMaxLengthSeconds(0.4f).build(); mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext()); @@ -1875,6 +1727,7 @@ public final class NotificationPanelViewController { } } + @Override public void collapse(boolean delayed, float speedUpFactor) { if (!canPanelBeCollapsed()) { return; @@ -1884,20 +1737,7 @@ public final class NotificationPanelViewController { setQsExpandImmediate(true); setShowShelfOnly(true); } - if (DEBUG) this.logf("collapse: " + this); - if (canPanelBeCollapsed()) { - cancelHeightAnimator(); - notifyExpandingStarted(); - - // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. - setIsClosing(true); - if (delayed) { - mNextCollapseSpeedUpFactor = speedUpFactor; - this.mView.postDelayed(mFlingCollapseRunnable, 120); - } else { - fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); - } - } + super.collapse(delayed, speedUpFactor); } private void setQsExpandImmediate(boolean expandImmediate) { @@ -1915,15 +1755,10 @@ public final class NotificationPanelViewController { setQsExpansion(mQsMinExpansionHeight); } + @Override @VisibleForTesting - void cancelHeightAnimator() { - if (mHeightAnimator != null) { - if (mHeightAnimator.isRunning()) { - mPanelUpdateWhenAnimatorEnds = false; - } - mHeightAnimator.cancel(); - } - endClosing(); + protected void cancelHeightAnimator() { + super.cancelHeightAnimator(); } public void cancelAnimation() { @@ -1991,123 +1826,28 @@ public final class NotificationPanelViewController { } } + @Override public void fling(float vel, boolean expand) { GestureRecorder gr = mCentralSurfaces.getGestureRecorder(); if (gr != null) { gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } - fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); + super.fling(vel, expand); } - @VisibleForTesting - void flingToHeight(float vel, boolean expand, float target, + @Override + protected void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { mHeadsUpTouchHelper.notifyFling(!expand); mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */); setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f); mNotificationStackScrollLayoutController.setPanelFlinging(true); - if (target == mExpandedHeight && mOverExpansion == 0.0f) { - // We're at the target and didn't fling and there's no overshoot - onFlingEnd(false /* cancelled */); - return; - } - mIsFlinging = true; - // we want to perform an overshoot animation when flinging open - final boolean addOverscroll = - expand - && !mInSplitShade // Split shade has its own overscroll logic - && mStatusBarStateController.getState() != KEYGUARD - && mOverExpansion == 0.0f - && vel >= 0; - final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); - float overshootAmount = 0.0f; - if (addOverscroll) { - // Let's overshoot depending on the amount of velocity - overshootAmount = MathUtils.lerp( - 0.2f, - 1.0f, - MathUtils.saturate(vel - / (this.mFlingAnimationUtils.getHighVelocityPxPerSecond() - * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT))); - overshootAmount += mOverExpansion / mPanelFlingOvershootAmount; - } - ValueAnimator animator = createHeightAnimator(target, overshootAmount); - if (expand) { - if (expandBecauseOfFalsing && vel < 0) { - vel = 0; - } - this.mFlingAnimationUtils.apply(animator, mExpandedHeight, - target + overshootAmount * mPanelFlingOvershootAmount, vel, - this.mView.getHeight()); - if (vel == 0) { - animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); - } - } else { - if (shouldUseDismissingAnimation()) { - if (vel == 0) { - animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); - long duration = (long) (200 + mExpandedHeight / this.mView.getHeight() * 100); - animator.setDuration(duration); - } else { - mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel, - this.mView.getHeight()); - } - } else { - mFlingAnimationUtilsClosing.apply( - animator, mExpandedHeight, target, vel, this.mView.getHeight()); - } - - // Make it shorter if we run a canned animation - if (vel == 0) { - animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); - } - if (mFixedDuration != NO_FIXED_DURATION) { - animator.setDuration(mFixedDuration); - } - } - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationStart(Animator animation) { - if (!mStatusBarStateController.isDozing()) { - beginJankMonitoring(); - } - } - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (shouldSpringBack && !mCancelled) { - // After the shade is flinged open to an overscrolled state, spring back - // the shade by reducing section padding to 0. - springBack(); - } else { - onFlingEnd(mCancelled); - } - } - }); - setAnimator(animator); - animator.start(); + super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } - private void onFlingEnd(boolean cancelled) { - mIsFlinging = false; - // No overshoot when the animation ends - setOverExpansionInternal(0, false /* isFromGesture */); - setAnimator(null); - mKeyguardStateController.notifyPanelFlingEnd(); - if (!cancelled) { - endJankMonitoring(); - notifyExpandingFinished(); - } else { - cancelJankMonitoring(); - } - updatePanelExpansionAndVisibility(); + @Override + protected void onFlingEnd(boolean cancelled) { + super.onFlingEnd(cancelled); mNotificationStackScrollLayoutController.setPanelFlinging(false); } @@ -2202,7 +1942,8 @@ public final class NotificationPanelViewController { return mQsTracking; } - private boolean isInContentBounds(float x, float y) { + @Override + protected boolean isInContentBounds(float x, float y) { float stackScrollerX = mNotificationStackScrollLayoutController.getX(); return !mNotificationStackScrollLayoutController .isBelowLastNotification(x - stackScrollerX, y) @@ -2335,8 +2076,9 @@ public final class NotificationPanelViewController { - mQsMinExpansionHeight)); } - private boolean shouldExpandWhenNotFlinging() { - if (getExpandedFraction() > 0.5f) { + @Override + protected boolean shouldExpandWhenNotFlinging() { + if (super.shouldExpandWhenNotFlinging()) { return true; } if (mAllowExpandForSmallExpansion) { @@ -2348,7 +2090,8 @@ public final class NotificationPanelViewController { return false; } - private float getOpeningHeight() { + @Override + protected float getOpeningHeight() { return mNotificationStackScrollLayoutController.getOpeningHeight(); } @@ -2498,20 +2241,9 @@ public final class NotificationPanelViewController { } } - private boolean flingExpands(float vel, float vectorVel, float x, float y) { - boolean expands = true; - if (!this.mFalsingManager.isUnlockingDisabled()) { - @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0 - ? QUICK_SETTINGS : ( - mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); - if (!isFalseTouch(x, y, interactionType)) { - if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - expands = shouldExpandWhenNotFlinging(); - } else { - expands = vel > 0; - } - } - } + @Override + protected boolean flingExpands(float vel, float vectorVel, float x, float y) { + boolean expands = super.flingExpands(vel, vectorVel, x, y); // If we are already running a QS expansion, make sure that we keep the panel open. if (mQsExpansionAnimator != null) { @@ -2520,7 +2252,8 @@ public final class NotificationPanelViewController { return expands; } - private boolean shouldGestureWaitForTouchSlop() { + @Override + protected boolean shouldGestureWaitForTouchSlop() { if (mExpectingSynthesizedDown) { mExpectingSynthesizedDown = false; return false; @@ -2598,7 +2331,7 @@ public final class NotificationPanelViewController { } } - private int getFalsingThreshold() { + protected int getFalsingThreshold() { float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f; return (int) (mQsFalsingThreshold * factor); } @@ -3332,8 +3065,8 @@ public final class NotificationPanelViewController { } } - @VisibleForTesting - boolean canCollapsePanelOnTouch() { + @Override + protected boolean canCollapsePanelOnTouch() { if (!isInSettings() && mBarState == KEYGUARD) { return true; } @@ -3345,6 +3078,7 @@ public final class NotificationPanelViewController { return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS); } + @Override public int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) @@ -3387,7 +3121,8 @@ public final class NotificationPanelViewController { return mIsExpanding; } - private void onHeightUpdated(float expandedHeight) { + @Override + protected void onHeightUpdated(float expandedHeight) { if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { // Updating the clock position will set the top padding which might // trigger a new panel height and re-position the clock. @@ -3569,7 +3304,9 @@ public final class NotificationPanelViewController { mLockIconViewController.setAlpha(alpha); } - private void onExpandingStarted() { + @Override + protected void onExpandingStarted() { + super.onExpandingStarted(); mNotificationStackScrollLayoutController.onExpansionStarted(); mIsExpanding = true; mQsExpandedWhenExpandingStarted = mQsFullyExpanded; @@ -3585,7 +3322,8 @@ public final class NotificationPanelViewController { mQs.setHeaderListening(true); } - private void onExpandingFinished() { + @Override + protected void onExpandingFinished() { mScrimController.onExpandingFinished(); mNotificationStackScrollLayoutController.onExpansionStopped(); mHeadsUpManager.onExpandingFinished(); @@ -3633,54 +3371,18 @@ public final class NotificationPanelViewController { mQs.setListening(listening); } + @Override public void expand(boolean animate) { - if (isFullyCollapsed() || isCollapsing()) { - mInstantExpanding = true; - mAnimateAfterExpanding = animate; - mUpdateFlingOnLayout = false; - abortAnimations(); - if (mTracking) { - onTrackingStopped(true /* expands */); // The panel is expanded after this call. - } - if (mExpanding) { - notifyExpandingFinished(); - } - updatePanelExpansionAndVisibility();// Wait for window manager to pickup the change, - // so we know the maximum height of the panel then. - this.mView.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (!mInstantExpanding) { - mView.getViewTreeObserver().removeOnGlobalLayoutListener( - this); - return; - } - if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) { - mView.getViewTreeObserver().removeOnGlobalLayoutListener( - this); - if (mAnimateAfterExpanding) { - notifyExpandingStarted(); - beginJankMonitoring(); - fling(0, true /* expand */); - } else { - setExpandedFraction(1f); - } - mInstantExpanding = false; - } - } - });// Make sure a layout really happens. - this.mView.requestLayout(); - } - + super.expand(animate); setListening(true); } + @Override public void setOverExpansion(float overExpansion) { if (overExpansion == mOverExpansion) { return; } - mOverExpansion = overExpansion; + super.setOverExpansion(overExpansion); // Translating the quick settings by half the overexpansion to center it in the background // frame updateQsFrameTranslation(); @@ -3692,13 +3394,10 @@ public final class NotificationPanelViewController { mQsTranslationForFullShadeTransition); } - private void onTrackingStarted() { + @Override + protected void onTrackingStarted() { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); - endClosing(); - mTracking = true; - mCentralSurfaces.onTrackingStarted(); - notifyExpandingStarted(); - updatePanelExpansionAndVisibility(); + super.onTrackingStarted(); mScrimController.onTrackingStarted(); if (mQsFullyExpanded) { setQsExpandImmediate(true); @@ -3708,11 +3407,10 @@ public final class NotificationPanelViewController { cancelPendingPanelCollapse(); } - private void onTrackingStopped(boolean expand) { + @Override + protected void onTrackingStopped(boolean expand) { mFalsingCollector.onTrackingStopped(); - mTracking = false; - mCentralSurfaces.onTrackingStopped(expand); - updatePanelExpansionAndVisibility(); + super.onTrackingStopped(expand); if (expand) { mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */, true /* animate */); @@ -3729,50 +3427,38 @@ public final class NotificationPanelViewController { getHeight(), mNavigationBarBottomHeight); } - @VisibleForTesting - void startUnlockHintAnimation() { + @Override + protected void startUnlockHintAnimation() { if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) { onUnlockHintStarted(); onUnlockHintFinished(); return; } - - // We don't need to hint the user if an animation is already running or the user is changing - // the expansion. - if (mHeightAnimator != null || mTracking) { - return; - } - notifyExpandingStarted(); - startUnlockHintAnimationPhase1(() -> { - notifyExpandingFinished(); - onUnlockHintFinished(); - mHintAnimationRunning = false; - }); - onUnlockHintStarted(); - mHintAnimationRunning = true; + super.startUnlockHintAnimation(); } - @VisibleForTesting - void onUnlockHintFinished() { - mCentralSurfaces.onHintFinished(); + @Override + protected void onUnlockHintFinished() { + super.onUnlockHintFinished(); mScrimController.setExpansionAffectsAlpha(true); mNotificationStackScrollLayoutController.setUnlockHintRunning(false); } - @VisibleForTesting - void onUnlockHintStarted() { - mCentralSurfaces.onUnlockHintStarted(); + @Override + protected void onUnlockHintStarted() { + super.onUnlockHintStarted(); mScrimController.setExpansionAffectsAlpha(false); mNotificationStackScrollLayoutController.setUnlockHintRunning(true); } - private boolean shouldUseDismissingAnimation() { + @Override + protected boolean shouldUseDismissingAnimation() { return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen() || !isTracking()); } - @VisibleForTesting - boolean isTrackingBlocked() { + @Override + protected boolean isTrackingBlocked() { return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; } @@ -3794,17 +3480,19 @@ public final class NotificationPanelViewController { return mIsLaunchTransitionFinished; } + @Override public void setIsLaunchAnimationRunning(boolean running) { boolean wasRunning = mIsLaunchAnimationRunning; - mIsLaunchAnimationRunning = running; + super.setIsLaunchAnimationRunning(running); if (wasRunning != mIsLaunchAnimationRunning) { mPanelEventsEmitter.notifyLaunchingActivityChanged(running); } } - private void setIsClosing(boolean isClosing) { + @Override + protected void setIsClosing(boolean isClosing) { boolean wasClosing = isClosing(); - mClosing = isClosing; + super.setIsClosing(isClosing); if (wasClosing != isClosing) { mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing); } @@ -3818,6 +3506,7 @@ public final class NotificationPanelViewController { } } + @Override public boolean isDozing() { return mDozing; } @@ -3834,7 +3523,8 @@ public final class NotificationPanelViewController { mKeyguardStatusViewController.dozeTimeTick(); } - private boolean onMiddleClicked() { + @Override + protected boolean onMiddleClicked() { switch (mBarState) { case KEYGUARD: if (!mDozingOnDown) { @@ -3892,13 +3582,15 @@ public final class NotificationPanelViewController { updateVisibility(); } - private boolean shouldPanelBeVisible() { + @Override + protected boolean shouldPanelBeVisible() { boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode; return headsUpVisible || isExpanded() || mBouncerShowing; } + @Override public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { - mHeadsUpManager = headsUpManager; + super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScrollLayoutController.getHeadsUpCallback(), NotificationPanelViewController.this); @@ -3912,7 +3604,8 @@ public final class NotificationPanelViewController { // otherwise we update the state when the expansion is finished } - private void onClosingFinished() { + @Override + protected void onClosingFinished() { mCentralSurfaces.onClosingFinished(); setClosingWithAlphaFadeout(false); mMediaHierarchyManager.closeGuts(); @@ -3976,7 +3669,8 @@ public final class NotificationPanelViewController { mCentralSurfaces.clearNotificationEffects(); } - private boolean isPanelVisibleBecauseOfHeadsUp() { + @Override + protected boolean isPanelVisibleBecauseOfHeadsUp() { return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway) && mBarState == StatusBarState.SHADE; } @@ -4090,15 +3784,9 @@ public final class NotificationPanelViewController { mNotificationBoundsAnimationDelay = delay; } + @Override public void setTouchAndAnimationDisabled(boolean disabled) { - mTouchDisabled = disabled; - if (mTouchDisabled) { - cancelHeightAnimator(); - if (mTracking) { - onTrackingStopped(true /* expanded */); - } - notifyExpandingFinished(); - } + super.setTouchAndAnimationDisabled(disabled); mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled); } @@ -4300,14 +3988,9 @@ public final class NotificationPanelViewController { mBlockingExpansionForCurrentTouch = mTracking; } + @Override public void dump(PrintWriter pw, String[] args) { - pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" - + " tracking=%s timeAnim=%s%s " - + "touchDisabled=%s" + "]", - this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(), - mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator, - ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""), - mTouchDisabled ? "T" : "f")); + super.dump(pw, args); IndentingPrintWriter ipw = asIndenting(pw); ipw.increaseIndent(); ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect()); @@ -4450,359 +4133,127 @@ public final class NotificationPanelViewController { mConfigurationListener.onThemeChanged(); } - private OnLayoutChangeListener createLayoutChangeListener() { - return new OnLayoutChangeListener(); - } - - @VisibleForTesting - TouchHandler createTouchHandler() { - return new TouchHandler(); + @Override + protected OnLayoutChangeListener createLayoutChangeListener() { + return new OnLayoutChangeListenerImpl(); } - public class TouchHandler implements View.OnTouchListener { + @Override + protected TouchHandler createTouchHandler() { + return new TouchHandler() { - private long mLastTouchDownTime = -1L; + private long mLastTouchDownTime = -1L; - public boolean onInterceptTouchEvent(MotionEvent event) { - if (SPEW_LOGCAT) { - Log.v(TAG, - "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() - + "," + event.getY() + ")"); - } - if (mBlockTouches || mQs.disallowPanelTouches()) { - return false; - } - initDownStates(event); - // Do not let touches go to shade or QS if the bouncer is visible, - // but still let user swipe down to expand the panel, dismissing the bouncer. - if (mCentralSurfaces.isBouncerShowing()) { - return true; - } - if (mCommandQueue.panelsEnabled() - && !mNotificationStackScrollLayoutController.isLongPressInProgress() - && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { - mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); - return true; - } - if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) - && mPulseExpansionHandler.onInterceptTouchEvent(event)) { - return true; - } + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (SPEW_LOGCAT) { + Log.v(TAG, + "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() + + "," + event.getY() + ")"); + } + if (mBlockTouches || mQs.disallowPanelTouches()) { + return false; + } + initDownStates(event); + // Do not let touches go to shade or QS if the bouncer is visible, + // but still let user swipe down to expand the panel, dismissing the bouncer. + if (mCentralSurfaces.isBouncerShowing()) { + return true; + } + if (mCommandQueue.panelsEnabled() + && !mNotificationStackScrollLayoutController.isLongPressInProgress() + && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); + mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + return true; + } + if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) + && mPulseExpansionHandler.onInterceptTouchEvent(event)) { + return true; + } - if (!isFullyCollapsed() && onQsIntercept(event)) { - if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); - return true; - } - if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted - && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { - return false; + if (!isFullyCollapsed() && onQsIntercept(event)) { + if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); + return true; + } + return super.onInterceptTouchEvent(event); } - /* - * If the user drags anywhere inside the panel we intercept it if the movement is - * upwards. This allows closing the shade from anywhere inside the panel. - * - * We only do this if the current content is scrolled to the bottom, - * i.e. canCollapsePanelOnTouch() is true and therefore there is no conflicting - * scrolling gesture possible. - */ - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); - } - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); - boolean canCollapsePanel = canCollapsePanelOnTouch(); - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mCentralSurfaces.userActivity(); - mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; - mMinExpandHeight = 0.0f; - mDownTime = mSystemClock.uptimeMillis(); - if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { - cancelHeightAnimator(); - mTouchSlopExceeded = true; + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event.getDownTime() == mLastTouchDownTime) { + // An issue can occur when swiping down after unlock, where multiple down + // events are received in this handler with identical downTimes. Until the + // source of the issue can be located, detect this case and ignore. + // see b/193350347 + Log.w(TAG, "Duplicate down event detected... ignoring"); return true; } - mInitialExpandY = y; - mInitialExpandX = x; - mTouchStartedInEmptyArea = !isInContentBounds(x, y); - mTouchSlopExceeded = mTouchSlopExceededBeforeDown; - mMotionAborted = false; - mPanelClosedOnDown = isFullyCollapsed(); - mCollapsedAndHeadsUpOnDown = false; - mHasLayoutedSinceDown = false; - mUpdateFlingOnLayout = false; - mTouchAboveFalsingThreshold = false; - addMovement(event); - break; - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - mTrackingPointer = event.getPointerId(newIndex); - mInitialExpandX = event.getX(newIndex); - mInitialExpandY = event.getY(newIndex); - } - break; - case MotionEvent.ACTION_POINTER_DOWN: - if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { - mMotionAborted = true; - mVelocityTracker.clear(); - } - break; - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialExpandY; - addMovement(event); - final boolean openShadeWithoutHun = - mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; - if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown - || openShadeWithoutHun) { - float hAbs = Math.abs(h); - float touchSlop = getTouchSlop(event); - if ((h < -touchSlop - || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) - && hAbs > Math.abs(x - mInitialExpandX)) { - cancelHeightAnimator(); - startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); - return true; - } - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mVelocityTracker.clear(); - break; - } - return false; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (event.getDownTime() == mLastTouchDownTime) { - // An issue can occur when swiping down after unlock, where multiple down - // events are received in this handler with identical downTimes. Until the - // source of the issue can be located, detect this case and ignore. - // see b/193350347 - Log.w(TAG, "Duplicate down event detected... ignoring"); - return true; + mLastTouchDownTime = event.getDownTime(); } - mLastTouchDownTime = event.getDownTime(); - } - - - if (mBlockTouches || (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches())) { - return false; - } - - // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, - // otherwise user would be able to pull down QS or expand the shade. - if (mCentralSurfaces.isBouncerShowingScrimmed() - || mCentralSurfaces.isBouncerShowingOverDream()) { - return false; - } - - // Make sure the next touch won't the blocked after the current ends. - if (event.getAction() == MotionEvent.ACTION_UP - || event.getAction() == MotionEvent.ACTION_CANCEL) { - mBlockingExpansionForCurrentTouch = false; - } - // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately - // without any ACTION_MOVE event. - // In such case, simply expand the panel instead of being stuck at the bottom bar. - if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) { - expand(true /* animate */); - } - initDownStates(event); - - // If pulse is expanding already, let's give it the touch. There are situations - // where the panel starts expanding even though we're also pulsing - boolean pulseShouldGetTouch = (!mIsExpanding - && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)) - || mPulseExpansionHandler.isExpanding(); - if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { - // We're expanding all the other ones shouldn't get this anymore - mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); - return true; - } - if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() - && !mNotificationStackScrollLayoutController.isLongPressInProgress() - && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { - mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); - } - boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); - if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { - mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); - return true; - } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { - mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - handled = true; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded() - && mStatusBarKeyguardViewManager.isShowing()) { - mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); - } - handled |= handleTouch(v, event); - return !mDozing || mPulsing || handled; - } - public boolean handleTouch(View v, MotionEvent event) { - if (mInstantExpanding) { - mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding"); - return false; - } - if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) { - mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled"); - return false; - } - if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) { - mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted"); - return false; - } + if (mBlockTouches || (mQsFullyExpanded && mQs != null + && mQs.disallowPanelTouches())) { + return false; + } - // If dragging should not expand the notifications shade, then return false. - if (!mNotificationsDragEnabled) { - if (mTracking) { - // Turn off tracking if it's on or the shade can get stuck in the down position. - onTrackingStopped(true /* expand */); + // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, + // otherwise user would be able to pull down QS or expand the shade. + if (mCentralSurfaces.isBouncerShowingScrimmed() + || mCentralSurfaces.isBouncerShowingOverDream()) { + return false; } - mShadeLog.logMotionEvent(event, "onTouch: drag not enabled"); - return false; - } - // On expanding, single mouse click expands the panel instead of dragging. - if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { - if (event.getAction() == MotionEvent.ACTION_UP) { - expand(true); + // Make sure the next touch won't the blocked after the current ends. + if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + mBlockingExpansionForCurrentTouch = false; } - return true; - } + // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately + // without any ACTION_MOVE event. + // In such case, simply expand the panel instead of being stuck at the bottom bar. + if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) { + expand(true /* animate */); + } + initDownStates(event); + + // If pulse is expanding already, let's give it the touch. There are situations + // where the panel starts expanding even though we're also pulsing + boolean pulseShouldGetTouch = (!mIsExpanding + && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)) + || mPulseExpansionHandler.isExpanding(); + if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { + // We're expanding all the other ones shouldn't get this anymore + mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event"); + return true; + } + if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() + && !mNotificationStackScrollLayoutController.isLongPressInProgress() + && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + } + boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); - /* - * We capture touch events here and update the expand height here in case according to - * the users fingers. This also handles multi-touch. - * - * Flinging is also enabled in order to open or close the shade. - */ - - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); - } - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { + mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); + return true; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { + mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); + handled = true; + } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); - mIgnoreXTouchSlop = true; - } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded() + && mStatusBarKeyguardViewManager.isShowing()) { + mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); + } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); - mMinExpandHeight = 0.0f; - mPanelClosedOnDown = isFullyCollapsed(); - mHasLayoutedSinceDown = false; - mUpdateFlingOnLayout = false; - mMotionAborted = false; - mDownTime = mSystemClock.uptimeMillis(); - mTouchAboveFalsingThreshold = false; - mCollapsedAndHeadsUpOnDown = - isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); - addMovement(event); - boolean regularHeightAnimationRunning = mHeightAnimator != null - && !mHintAnimationRunning && !mIsSpringBackAnimation; - if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { - mTouchSlopExceeded = regularHeightAnimationRunning - || mTouchSlopExceededBeforeDown; - cancelHeightAnimator(); - onTrackingStarted(); - } - if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() - && !mCentralSurfaces.isBouncerShowing()) { - startOpening(event); - } - break; - - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mTrackingPointer = event.getPointerId(newIndex); - mHandlingPointerUp = true; - startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); - mHandlingPointerUp = false; - } - break; - case MotionEvent.ACTION_POINTER_DOWN: - if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { - mMotionAborted = true; - endMotionEvent(event, x, y, true /* forceCancel */); - return false; - } - break; - case MotionEvent.ACTION_MOVE: - addMovement(event); - float h = y - mInitialTouchY; - - // If the panel was collapsed when touching, we only need to check for the - // y-component of the gesture, as we have no conflicting horizontal gesture. - if (Math.abs(h) > getTouchSlop(event) - && (Math.abs(h) > Math.abs(x - mInitialTouchX) - || mIgnoreXTouchSlop)) { - mTouchSlopExceeded = true; - if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { - if (mInitialOffsetOnTouch != 0f) { - startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); - h = 0; - } - cancelHeightAnimator(); - onTrackingStarted(); - } - } - float newHeight = Math.max(0, h + mInitialOffsetOnTouch); - newHeight = Math.max(newHeight, mMinExpandHeight); - if (-h >= getFalsingThreshold()) { - mTouchAboveFalsingThreshold = true; - mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); - } - if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { - // Count h==0 as part of swipe-up, - // otherwise {@link NotificationStackScrollLayout} - // wrongly enables stack height updates at the start of lockscreen swipe-up - mAmbientState.setSwipingUp(h <= 0); - setExpandedHeightInternal(newHeight); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - addMovement(event); - endMotionEvent(event, x, y, false /* forceCancel */); - // mHeightAnimator is null, there is no remaining frame, ends instrumenting. - if (mHeightAnimator == null) { - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - endJankMonitoring(); - } else { - cancelJankMonitoring(); - } - } - break; + handled |= super.onTouch(v, event); + return !mDozing || mPulsing || handled; } - return !mGestureWaitForTouchSlop || mTracking; - } + }; } private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler = @@ -4854,7 +4305,8 @@ public final class NotificationPanelViewController { } }; - private OnConfigurationChangedListener createOnConfigurationChangedListener() { + @Override + protected OnConfigurationChangedListener createOnConfigurationChangedListener() { return new OnConfigurationChangedListener(); } @@ -4916,585 +4368,6 @@ public final class NotificationPanelViewController { .commitUpdate(mDisplayId); } - private void logf(String fmt, Object... args) { - Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); - } - - private void notifyExpandingStarted() { - if (!mExpanding) { - mExpanding = true; - onExpandingStarted(); - } - } - - private void notifyExpandingFinished() { - endClosing(); - if (mExpanding) { - mExpanding = false; - onExpandingFinished(); - } - } - - private float getTouchSlop(MotionEvent event) { - // Adjust the touch slop if another gesture may be being performed. - return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE - ? mTouchSlop * mSlopMultiplier - : mTouchSlop; - } - - private void addMovement(MotionEvent event) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - float deltaX = event.getRawX() - event.getX(); - float deltaY = event.getRawY() - event.getY(); - event.offsetLocation(deltaX, deltaY); - mVelocityTracker.addMovement(event); - event.offsetLocation(-deltaX, -deltaY); - } - - public void startExpandLatencyTracking() { - if (mLatencyTracker.isEnabled()) { - mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL); - mExpandLatencyTracking = true; - } - } - - private void startOpening(MotionEvent event) { - updatePanelExpansionAndVisibility(); - maybeVibrateOnOpening(); - - //TODO: keyguard opens QS a different way; log that too? - - // Log the position of the swipe that opened the panel - float width = mCentralSurfaces.getDisplayWidth(); - float height = mCentralSurfaces.getDisplayHeight(); - int rot = mCentralSurfaces.getRotation(); - - mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, - (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); - mLockscreenGestureLogger - .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); - } - - private void maybeVibrateOnOpening() { - if (mVibrateOnOpening) { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); - } - } - - /** - * @return whether the swiping direction is upwards and above a 45 degree angle compared to the - * horizontal direction - */ - private boolean isDirectionUpwards(float x, float y) { - float xDiff = x - mInitialExpandX; - float yDiff = y - mInitialExpandY; - if (yDiff >= 0) { - return false; - } - return Math.abs(yDiff) >= Math.abs(xDiff); - } - - public void startExpandMotion(float newX, float newY, boolean startTracking, - float expandedHeight) { - if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { - beginJankMonitoring(); - } - mInitialOffsetOnTouch = expandedHeight; - mInitialExpandY = newY; - mInitialExpandX = newX; - mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; - if (startTracking) { - mTouchSlopExceeded = true; - setExpandedHeight(mInitialOffsetOnTouch); - onTrackingStarted(); - } - } - - private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { - mTrackingPointer = -1; - mAmbientState.setSwipingUp(false); - if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop - || Math.abs(y - mInitialExpandY) > mTouchSlop - || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { - mVelocityTracker.computeCurrentVelocity(1000); - float vel = mVelocityTracker.getYVelocity(); - float vectorVel = (float) Math.hypot( - mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - - final boolean onKeyguard = - mStatusBarStateController.getState() == StatusBarState.KEYGUARD; - - final boolean expand; - if (mKeyguardStateController.isKeyguardFadingAway() - || (mInitialTouchFromKeyguard && !onKeyguard)) { - // Don't expand for any touches that started from the keyguard and ended after the - // keyguard is gone. - expand = false; - } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { - if (onKeyguard) { - expand = true; - } else if (mCentralSurfaces.isBouncerShowingOverDream()) { - expand = false; - } else { - // If we get a cancel, put the shade back to the state it was in when the - // gesture started - expand = !mPanelClosedOnDown; - } - } else { - expand = flingExpands(vel, vectorVel, x, y); - } - - mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, - mCentralSurfaces.isFalsingThresholdNeeded(), - mCentralSurfaces.isWakeUpComingFromTouch()); - // Log collapse gesture if on lock screen. - if (!expand && onKeyguard) { - float displayDensity = mCentralSurfaces.getDisplayDensity(); - int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity); - int velocityDp = (int) Math.abs(vel / displayDensity); - mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); - mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); - } - @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC - : y - mInitialExpandY > 0 ? QUICK_SETTINGS - : (mKeyguardStateController.canDismissLockScreen() - ? UNLOCK : BOUNCER_UNLOCK); - - fling(vel, expand, isFalseTouch(x, y, interactionType)); - onTrackingStopped(expand); - mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; - if (mUpdateFlingOnLayout) { - mUpdateFlingVelocity = vel; - } - } else if (!mCentralSurfaces.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() - && !mKeyguardStateController.isKeyguardGoingAway()) { - boolean expands = onEmptySpaceClick(); - onTrackingStopped(expands); - } - mVelocityTracker.clear(); - } - - private float getCurrentExpandVelocity() { - mVelocityTracker.computeCurrentVelocity(1000); - return mVelocityTracker.getYVelocity(); - } - - private void endClosing() { - if (mClosing) { - setIsClosing(false); - onClosingFinished(); - } - } - - /** - * @param x the final x-coordinate when the finger was lifted - * @param y the final y-coordinate when the finger was lifted - * @return whether this motion should be regarded as a false touch - */ - private boolean isFalseTouch(float x, float y, - @Classifier.InteractionType int interactionType) { - if (!mCentralSurfaces.isFalsingThresholdNeeded()) { - return false; - } - if (mFalsingManager.isClassifierEnabled()) { - return mFalsingManager.isFalseTouch(interactionType); - } - if (!mTouchAboveFalsingThreshold) { - return true; - } - if (mUpwardsWhenThresholdReached) { - return false; - } - return !isDirectionUpwards(x, y); - } - - private void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { - fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); - } - - private void fling(float vel, boolean expand, float collapseSpeedUpFactor, - boolean expandBecauseOfFalsing) { - float target = expand ? getMaxPanelHeight() : 0; - if (!expand) { - setIsClosing(true); - } - flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); - } - - private void springBack() { - if (mOverExpansion == 0) { - onFlingEnd(false /* cancelled */); - return; - } - mIsSpringBackAnimation = true; - ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0); - animator.addUpdateListener( - animation -> setOverExpansionInternal((float) animation.getAnimatedValue(), - false /* isFromGesture */)); - animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - mIsSpringBackAnimation = false; - onFlingEnd(mCancelled); - } - }); - setAnimator(animator); - animator.start(); - } - - public String getName() { - return mViewName; - } - - public void setExpandedHeight(float height) { - if (DEBUG) logf("setExpandedHeight(%.1f)", height); - setExpandedHeightInternal(height); - } - - private void requestPanelHeightUpdate() { - float currentMaxPanelHeight = getMaxPanelHeight(); - - if (isFullyCollapsed()) { - return; - } - - if (currentMaxPanelHeight == mExpandedHeight) { - return; - } - - if (mTracking && !isTrackingBlocked()) { - return; - } - - if (mHeightAnimator != null && !mIsSpringBackAnimation) { - mPanelUpdateWhenAnimatorEnds = true; - return; - } - - setExpandedHeight(currentMaxPanelHeight); - } - - public void setExpandedHeightInternal(float h) { - if (isNaN(h)) { - Log.wtf(TAG, "ExpandedHeight set to NaN"); - } - mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> { - if (mExpandLatencyTracking && h != 0f) { - DejankUtils.postAfterTraversal( - () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); - mExpandLatencyTracking = false; - } - float maxPanelHeight = getMaxPanelHeight(); - if (mHeightAnimator == null) { - // Split shade has its own overscroll logic - if (mTracking && !mInSplitShade) { - float overExpansionPixels = Math.max(0, h - maxPanelHeight); - setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); - } - mExpandedHeight = Math.min(h, maxPanelHeight); - } else { - mExpandedHeight = h; - } - - // If we are closing the panel and we are almost there due to a slow decelerating - // interpolator, abort the animation. - if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { - mExpandedHeight = 0f; - if (mHeightAnimator != null) { - mHeightAnimator.end(); - } - } - mExpansionDragDownAmountPx = h; - mExpandedFraction = Math.min(1f, - maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); - mAmbientState.setExpansionFraction(mExpandedFraction); - onHeightUpdated(mExpandedHeight); - updatePanelExpansionAndVisibility(); - }); - } - - /** - * Set the current overexpansion - * - * @param overExpansion the amount of overexpansion to apply - * @param isFromGesture is this amount from a gesture and needs to be rubberBanded? - */ - private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) { - if (!isFromGesture) { - mLastGesturedOverExpansion = -1; - setOverExpansion(overExpansion); - } else if (mLastGesturedOverExpansion != overExpansion) { - mLastGesturedOverExpansion = overExpansion; - final float heightForFullOvershoot = mView.getHeight() / 3.0f; - float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot); - newExpansion = Interpolators.getOvershootInterpolation(newExpansion); - setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f); - } - } - - public void setExpandedFraction(float frac) { - setExpandedHeight(getMaxPanelHeight() * frac); - } - - public float getExpandedHeight() { - return mExpandedHeight; - } - - public float getExpandedFraction() { - return mExpandedFraction; - } - - public boolean isFullyExpanded() { - return mExpandedHeight >= getMaxPanelHeight(); - } - - public boolean isFullyCollapsed() { - return mExpandedFraction <= 0.0f; - } - - public boolean isCollapsing() { - return mClosing || mIsLaunchAnimationRunning; - } - - public boolean isFlinging() { - return mIsFlinging; - } - - public boolean isTracking() { - return mTracking; - } - - public boolean canPanelBeCollapsed() { - return !isFullyCollapsed() && !mTracking && !mClosing; - } - - private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, - mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); - - public void instantCollapse() { - abortAnimations(); - setExpandedFraction(0f); - if (mExpanding) { - notifyExpandingFinished(); - } - if (mInstantExpanding) { - mInstantExpanding = false; - updatePanelExpansionAndVisibility(); - } - } - - private void abortAnimations() { - cancelHeightAnimator(); - mView.removeCallbacks(mFlingCollapseRunnable); - } - - public boolean isUnlockHintRunning() { - return mHintAnimationRunning; - } - - /** - * Phase 1: Move everything upwards. - */ - private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { - float target = Math.max(0, getMaxPanelHeight() - mHintDistance); - ValueAnimator animator = createHeightAnimator(target); - animator.setDuration(250); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) { - setAnimator(null); - onAnimationFinished.run(); - } else { - startUnlockHintAnimationPhase2(onAnimationFinished); - } - } - }); - animator.start(); - setAnimator(animator); - - final List<ViewPropertyAnimator> indicationAnimators = - mKeyguardBottomArea.getIndicationAreaAnimators(); - for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) { - indicationAreaAnimator - .translationY(-mHintDistance) - .setDuration(250) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(() -> indicationAreaAnimator - .translationY(0) - .setDuration(450) - .setInterpolator(mBounceInterpolator) - .start()) - .start(); - } - } - - private void setAnimator(ValueAnimator animator) { - mHeightAnimator = animator; - if (animator == null && mPanelUpdateWhenAnimatorEnds) { - mPanelUpdateWhenAnimatorEnds = false; - requestPanelHeightUpdate(); - } - } - - /** - * Phase 2: Bounce down. - */ - private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { - ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); - animator.setDuration(450); - animator.setInterpolator(mBounceInterpolator); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setAnimator(null); - onAnimationFinished.run(); - updatePanelExpansionAndVisibility(); - } - }); - animator.start(); - setAnimator(animator); - } - - private ValueAnimator createHeightAnimator(float targetHeight) { - return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */); - } - - /** - * Create an animator that can also overshoot - * - * @param targetHeight the target height - * @param overshootAmount the amount of overshoot desired - */ - private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { - float startExpansion = mOverExpansion; - ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); - animator.addUpdateListener( - animation -> { - if (overshootAmount > 0.0f - // Also remove the overExpansion when collapsing - || (targetHeight == 0.0f && startExpansion != 0)) { - final float expansion = MathUtils.lerp( - startExpansion, - mPanelFlingOvershootAmount * overshootAmount, - Interpolators.FAST_OUT_SLOW_IN.getInterpolation( - animator.getAnimatedFraction())); - setOverExpansionInternal(expansion, false /* isFromGesture */); - } - setExpandedHeightInternal((float) animation.getAnimatedValue()); - }); - return animator; - } - - /** Update the visibility of {@link PanelView} if necessary. */ - public void updateVisibility() { - mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE); - } - - /** - * Updates the panel expansion and {@link PanelView} visibility if necessary. - * - * TODO(b/200063118): Could public calls to this method be replaced with calls to - * {@link #updateVisibility()}? That would allow us to make this method private. - */ - public void updatePanelExpansionAndVisibility() { - mPanelExpansionStateManager.onPanelExpansionChanged( - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); - updateVisibility(); - } - - public boolean isExpanded() { - return mExpandedFraction > 0f - || mInstantExpanding - || isPanelVisibleBecauseOfHeadsUp() - || mTracking - || mHeightAnimator != null - && !mIsSpringBackAnimation; - } - - /** - * Gets called when the user performs a click anywhere in the empty area of the panel. - * - * @return whether the panel will be expanded after the action performed by this method - */ - private boolean onEmptySpaceClick() { - if (mHintAnimationRunning) { - return true; - } - return onMiddleClicked(); - } - - @VisibleForTesting - boolean isClosing() { - return mClosing; - } - - public void collapseWithDuration(int animationDuration) { - mFixedDuration = animationDuration; - collapse(false /* delayed */, 1.0f /* speedUpFactor */); - mFixedDuration = NO_FIXED_DURATION; - } - - public ViewGroup getView() { - // TODO: remove this method, or at least reduce references to it. - return mView; - } - - private void beginJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.Configuration.Builder builder = - InteractionJankMonitor.Configuration.Builder.withView( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, - mView) - .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); - mInteractionJankMonitor.begin(builder); - } - - private void endJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().end( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); - } - - private void cancelJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().cancel( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); - } - - private float getExpansionFraction() { - return mExpandedFraction; - } - - private PanelExpansionStateManager getPanelExpansionStateManager() { - return mPanelExpansionStateManager; - } - private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener { @Override public void onHeightChanged(ExpandableView view, boolean needsAnimation) { @@ -5900,19 +4773,13 @@ public final class NotificationPanelViewController { } } - private class OnLayoutChangeListener implements View.OnLayoutChangeListener { + private class OnLayoutChangeListenerImpl extends OnLayoutChangeListener { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { DejankUtils.startDetectingBlockingIpcs("NVP#onLayout"); - requestPanelHeightUpdate(); - mHasLayoutedSinceDown = true; - if (mUpdateFlingOnLayout) { - abortAnimations(); - fling(mUpdateFlingVelocity, true /* expands */); - mUpdateFlingOnLayout = false; - } + super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom); updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); @@ -6168,12 +5035,4 @@ public final class NotificationPanelViewController { } } } - - public class OnConfigurationChangedListener implements - PanelView.OnConfigurationChangedListener { - @Override - public void onConfigurationChanged(Configuration newConfig) { - loadDimens(); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java deleted file mode 100644 index 4349d816b3c3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shade; - -import android.content.Context; -import android.content.res.Configuration; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; - -public abstract class PanelView extends FrameLayout { - public static final boolean DEBUG = false; - public static final String TAG = PanelView.class.getSimpleName(); - private NotificationPanelViewController.TouchHandler mTouchHandler; - - protected CentralSurfaces mCentralSurfaces; - protected HeadsUpManagerPhone mHeadsUpManager; - - protected KeyguardBottomAreaView mKeyguardBottomArea; - private OnConfigurationChangedListener mOnConfigurationChangedListener; - - public PanelView(Context context) { - super(context); - } - - public PanelView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public PanelView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) { - super.setOnTouchListener(touchHandler); - mTouchHandler = touchHandler; - } - - public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) { - mOnConfigurationChangedListener = listener; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mTouchHandler.onInterceptTouchEvent(event); - } - - @Override - public void dispatchConfigurationChanged(Configuration newConfig) { - super.dispatchConfigurationChanged(newConfig); - mOnConfigurationChangedListener.onConfigurationChanged(newConfig); - } - - interface OnConfigurationChangedListener { - void onConfigurationChanged(Configuration newConfig); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java new file mode 100644 index 000000000000..11e36c915adf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java @@ -0,0 +1,1479 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; + +import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; +import static com.android.systemui.classifier.Classifier.GENERIC; +import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; +import static com.android.systemui.classifier.Classifier.UNLOCK; +import static com.android.systemui.shade.NotificationPanelView.DEBUG; + +import static java.lang.Float.isNaN; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.VibrationEffect; +import android.util.Log; +import android.util.MathUtils; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.view.animation.Interpolator; + +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.LatencyTracker; +import com.android.systemui.DejankUtils; +import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; +import com.android.systemui.classifier.Classifier; +import com.android.systemui.doze.DozeLog; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.notification.stack.AmbientState; +import com.android.systemui.statusbar.phone.BounceInterpolator; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.time.SystemClock; +import com.android.wm.shell.animation.FlingAnimationUtils; + +import java.io.PrintWriter; +import java.util.List; + +abstract class PanelViewController { + public static final String TAG = NotificationPanelView.class.getSimpleName(); + public static final float FLING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_SPEED_UP_FACTOR = 0.6f; + public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f; + private static final int NO_FIXED_DURATION = -1; + private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; + private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; + + /** + * The factor of the usual high velocity that is needed in order to reach the maximum overshoot + * when flinging. A low value will make it that most flings will reach the maximum overshoot. + */ + private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; + + protected long mDownTime; + protected boolean mTouchSlopExceededBeforeDown; + private float mMinExpandHeight; + private boolean mPanelUpdateWhenAnimatorEnds; + private final boolean mVibrateOnOpening; + protected boolean mIsLaunchAnimationRunning; + private int mFixedDuration = NO_FIXED_DURATION; + protected float mOverExpansion; + + /** + * The overshoot amount when the panel flings open + */ + private float mPanelFlingOvershootAmount; + + /** + * The amount of pixels that we have overexpanded the last time with a gesture + */ + private float mLastGesturedOverExpansion = -1; + + /** + * Is the current animator the spring back animation? + */ + private boolean mIsSpringBackAnimation; + + private boolean mInSplitShade; + + private void logf(String fmt, Object... args) { + Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); + } + + protected CentralSurfaces mCentralSurfaces; + protected HeadsUpManagerPhone mHeadsUpManager; + protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + + private float mHintDistance; + private float mInitialOffsetOnTouch; + private boolean mCollapsedAndHeadsUpOnDown; + private float mExpandedFraction = 0; + private float mExpansionDragDownAmountPx = 0; + protected float mExpandedHeight = 0; + private boolean mPanelClosedOnDown; + private boolean mHasLayoutedSinceDown; + private float mUpdateFlingVelocity; + private boolean mUpdateFlingOnLayout; + private boolean mClosing; + protected boolean mTracking; + private boolean mTouchSlopExceeded; + private int mTrackingPointer; + private int mTouchSlop; + private float mSlopMultiplier; + protected boolean mHintAnimationRunning; + private boolean mTouchAboveFalsingThreshold; + private boolean mTouchStartedInEmptyArea; + private boolean mMotionAborted; + private boolean mUpwardsWhenThresholdReached; + private boolean mAnimatingOnDown; + private boolean mHandlingPointerUp; + + private ValueAnimator mHeightAnimator; + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + private final FlingAnimationUtils mFlingAnimationUtils; + private final FlingAnimationUtils mFlingAnimationUtilsClosing; + private final FlingAnimationUtils mFlingAnimationUtilsDismissing; + private final LatencyTracker mLatencyTracker; + private final FalsingManager mFalsingManager; + private final DozeLog mDozeLog; + private final VibratorHelper mVibratorHelper; + + /** + * Whether an instant expand request is currently pending and we are just waiting for layout. + */ + private boolean mInstantExpanding; + private boolean mAnimateAfterExpanding; + private boolean mIsFlinging; + + private String mViewName; + private float mInitialExpandY; + private float mInitialExpandX; + private boolean mTouchDisabled; + private boolean mInitialTouchFromKeyguard; + + /** + * Whether or not the NotificationPanelView can be expanded or collapsed with a drag. + */ + private final boolean mNotificationsDragEnabled; + + private final Interpolator mBounceInterpolator; + protected KeyguardBottomAreaView mKeyguardBottomArea; + + /** + * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. + */ + private float mNextCollapseSpeedUpFactor = 1.0f; + + protected boolean mExpanding; + private boolean mGestureWaitForTouchSlop; + private boolean mIgnoreXTouchSlop; + private boolean mExpandLatencyTracking; + private final NotificationPanelView mView; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final NotificationShadeWindowController mNotificationShadeWindowController; + protected final Resources mResources; + protected final KeyguardStateController mKeyguardStateController; + protected final SysuiStatusBarStateController mStatusBarStateController; + protected final AmbientState mAmbientState; + protected final LockscreenGestureLogger mLockscreenGestureLogger; + private final PanelExpansionStateManager mPanelExpansionStateManager; + private final InteractionJankMonitor mInteractionJankMonitor; + protected final SystemClock mSystemClock; + + protected final ShadeLogger mShadeLog; + + protected abstract void onExpandingFinished(); + + protected void onExpandingStarted() { + } + + protected void notifyExpandingStarted() { + if (!mExpanding) { + mExpanding = true; + onExpandingStarted(); + } + } + + protected final void notifyExpandingFinished() { + endClosing(); + if (mExpanding) { + mExpanding = false; + onExpandingFinished(); + } + } + + protected AmbientState getAmbientState() { + return mAmbientState; + } + + public PanelViewController( + NotificationPanelView view, + FalsingManager falsingManager, + DozeLog dozeLog, + KeyguardStateController keyguardStateController, + SysuiStatusBarStateController statusBarStateController, + NotificationShadeWindowController notificationShadeWindowController, + VibratorHelper vibratorHelper, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LatencyTracker latencyTracker, + FlingAnimationUtils.Builder flingAnimationUtilsBuilder, + StatusBarTouchableRegionManager statusBarTouchableRegionManager, + LockscreenGestureLogger lockscreenGestureLogger, + PanelExpansionStateManager panelExpansionStateManager, + AmbientState ambientState, + InteractionJankMonitor interactionJankMonitor, + ShadeLogger shadeLogger, + SystemClock systemClock) { + keyguardStateController.addCallback(new KeyguardStateController.Callback() { + @Override + public void onKeyguardFadingAwayChanged() { + requestPanelHeightUpdate(); + } + }); + mAmbientState = ambientState; + mView = view; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockscreenGestureLogger = lockscreenGestureLogger; + mPanelExpansionStateManager = panelExpansionStateManager; + mShadeLog = shadeLogger; + TouchHandler touchHandler = createTouchHandler(); + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mViewName = mResources.getResourceName(mView.getId()); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + + mView.addOnLayoutChangeListener(createLayoutChangeListener()); + mView.setOnTouchListener(touchHandler); + mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); + + mResources = mView.getResources(); + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = statusBarStateController; + mNotificationShadeWindowController = notificationShadeWindowController; + mFlingAnimationUtils = flingAnimationUtilsBuilder + .reset() + .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) + .build(); + mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder + .reset() + .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR) + .build(); + mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder + .reset() + .setMaxLengthSeconds(0.5f) + .setSpeedUpFactor(0.6f) + .setX2(0.6f) + .setY2(0.84f) + .build(); + mLatencyTracker = latencyTracker; + mBounceInterpolator = new BounceInterpolator(); + mFalsingManager = falsingManager; + mDozeLog = dozeLog; + mNotificationsDragEnabled = mResources.getBoolean( + R.bool.config_enableNotificationShadeDrag); + mVibratorHelper = vibratorHelper; + mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); + mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; + mInteractionJankMonitor = interactionJankMonitor; + mSystemClock = systemClock; + } + + protected void loadDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); + mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); + mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount); + mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade); + } + + protected float getTouchSlop(MotionEvent event) { + // Adjust the touch slop if another gesture may be being performed. + return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + } + + private void addMovement(MotionEvent event) { + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + + public void setTouchAndAnimationDisabled(boolean disabled) { + mTouchDisabled = disabled; + if (mTouchDisabled) { + cancelHeightAnimator(); + if (mTracking) { + onTrackingStopped(true /* expanded */); + } + notifyExpandingFinished(); + } + } + + public void startExpandLatencyTracking() { + if (mLatencyTracker.isEnabled()) { + mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL); + mExpandLatencyTracking = true; + } + } + + private void startOpening(MotionEvent event) { + updatePanelExpansionAndVisibility(); + maybeVibrateOnOpening(); + + //TODO: keyguard opens QS a different way; log that too? + + // Log the position of the swipe that opened the panel + float width = mCentralSurfaces.getDisplayWidth(); + float height = mCentralSurfaces.getDisplayHeight(); + int rot = mCentralSurfaces.getRotation(); + + mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, + (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); + mLockscreenGestureLogger + .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); + } + + protected void maybeVibrateOnOpening() { + if (mVibrateOnOpening) { + mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); + } + } + + protected abstract float getOpeningHeight(); + + /** + * @return whether the swiping direction is upwards and above a 45 degree angle compared to the + * horizontal direction + */ + private boolean isDirectionUpwards(float x, float y) { + float xDiff = x - mInitialExpandX; + float yDiff = y - mInitialExpandY; + if (yDiff >= 0) { + return false; + } + return Math.abs(yDiff) >= Math.abs(xDiff); + } + + public void startExpandMotion(float newX, float newY, boolean startTracking, + float expandedHeight) { + if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { + beginJankMonitoring(); + } + mInitialOffsetOnTouch = expandedHeight; + mInitialExpandY = newY; + mInitialExpandX = newX; + mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; + if (startTracking) { + mTouchSlopExceeded = true; + setExpandedHeight(mInitialOffsetOnTouch); + onTrackingStarted(); + } + } + + private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mTrackingPointer = -1; + mAmbientState.setSwipingUp(false); + if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop + || Math.abs(y - mInitialExpandY) > mTouchSlop + || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { + mVelocityTracker.computeCurrentVelocity(1000); + float vel = mVelocityTracker.getYVelocity(); + float vectorVel = (float) Math.hypot( + mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + final boolean onKeyguard = + mStatusBarStateController.getState() == StatusBarState.KEYGUARD; + + final boolean expand; + if (mKeyguardStateController.isKeyguardFadingAway() + || (mInitialTouchFromKeyguard && !onKeyguard)) { + // Don't expand for any touches that started from the keyguard and ended after the + // keyguard is gone. + expand = false; + } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { + if (onKeyguard) { + expand = true; + } else if (mCentralSurfaces.isBouncerShowingOverDream()) { + expand = false; + } else { + // If we get a cancel, put the shade back to the state it was in when the + // gesture started + expand = !mPanelClosedOnDown; + } + } else { + expand = flingExpands(vel, vectorVel, x, y); + } + + mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold, + mCentralSurfaces.isFalsingThresholdNeeded(), + mCentralSurfaces.isWakeUpComingFromTouch()); + // Log collapse gesture if on lock screen. + if (!expand && onKeyguard) { + float displayDensity = mCentralSurfaces.getDisplayDensity(); + int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity); + int velocityDp = (int) Math.abs(vel / displayDensity); + mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); + } + @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC + : y - mInitialExpandY > 0 ? QUICK_SETTINGS + : (mKeyguardStateController.canDismissLockScreen() + ? UNLOCK : BOUNCER_UNLOCK); + + fling(vel, expand, isFalseTouch(x, y, interactionType)); + onTrackingStopped(expand); + mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; + if (mUpdateFlingOnLayout) { + mUpdateFlingVelocity = vel; + } + } else if (!mCentralSurfaces.isBouncerShowing() + && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() + && !mKeyguardStateController.isKeyguardGoingAway()) { + boolean expands = onEmptySpaceClick(); + onTrackingStopped(expands); + } + mVelocityTracker.clear(); + } + + protected float getCurrentExpandVelocity() { + mVelocityTracker.computeCurrentVelocity(1000); + return mVelocityTracker.getYVelocity(); + } + + protected abstract int getFalsingThreshold(); + + protected abstract boolean shouldGestureWaitForTouchSlop(); + + protected void onTrackingStopped(boolean expand) { + mTracking = false; + mCentralSurfaces.onTrackingStopped(expand); + updatePanelExpansionAndVisibility(); + } + + protected void onTrackingStarted() { + endClosing(); + mTracking = true; + mCentralSurfaces.onTrackingStarted(); + notifyExpandingStarted(); + updatePanelExpansionAndVisibility(); + } + + /** + * @return Whether a pair of coordinates are inside the visible view content bounds. + */ + protected abstract boolean isInContentBounds(float x, float y); + + protected void cancelHeightAnimator() { + if (mHeightAnimator != null) { + if (mHeightAnimator.isRunning()) { + mPanelUpdateWhenAnimatorEnds = false; + } + mHeightAnimator.cancel(); + } + endClosing(); + } + + private void endClosing() { + if (mClosing) { + setIsClosing(false); + onClosingFinished(); + } + } + + protected abstract boolean canCollapsePanelOnTouch(); + + protected float getContentHeight() { + return mExpandedHeight; + } + + /** + * @param vel the current vertical velocity of the motion + * @param vectorVel the length of the vectorial velocity + * @return whether a fling should expands the panel; contracts otherwise + */ + protected boolean flingExpands(float vel, float vectorVel, float x, float y) { + if (mFalsingManager.isUnlockingDisabled()) { + return true; + } + + @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0 + ? QUICK_SETTINGS : ( + mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); + + if (isFalseTouch(x, y, interactionType)) { + return true; + } + if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + return shouldExpandWhenNotFlinging(); + } else { + return vel > 0; + } + } + + protected boolean shouldExpandWhenNotFlinging() { + return getExpandedFraction() > 0.5f; + } + + /** + * @param x the final x-coordinate when the finger was lifted + * @param y the final y-coordinate when the finger was lifted + * @return whether this motion should be regarded as a false touch + */ + private boolean isFalseTouch(float x, float y, + @Classifier.InteractionType int interactionType) { + if (!mCentralSurfaces.isFalsingThresholdNeeded()) { + return false; + } + if (mFalsingManager.isClassifierEnabled()) { + return mFalsingManager.isFalseTouch(interactionType); + } + if (!mTouchAboveFalsingThreshold) { + return true; + } + if (mUpwardsWhenThresholdReached) { + return false; + } + return !isDirectionUpwards(x, y); + } + + protected void fling(float vel, boolean expand) { + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); + } + + protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) { + fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing); + } + + protected void fling(float vel, boolean expand, float collapseSpeedUpFactor, + boolean expandBecauseOfFalsing) { + float target = expand ? getMaxPanelHeight() : 0; + if (!expand) { + setIsClosing(true); + } + flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); + } + + protected void flingToHeight(float vel, boolean expand, float target, + float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { + if (target == mExpandedHeight && mOverExpansion == 0.0f) { + // We're at the target and didn't fling and there's no overshoot + onFlingEnd(false /* cancelled */); + return; + } + mIsFlinging = true; + // we want to perform an overshoot animation when flinging open + final boolean addOverscroll = + expand + && !mInSplitShade // Split shade has its own overscroll logic + && mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && mOverExpansion == 0.0f + && vel >= 0; + final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand); + float overshootAmount = 0.0f; + if (addOverscroll) { + // Let's overshoot depending on the amount of velocity + overshootAmount = MathUtils.lerp( + 0.2f, + 1.0f, + MathUtils.saturate(vel + / (mFlingAnimationUtils.getHighVelocityPxPerSecond() + * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT))); + overshootAmount += mOverExpansion / mPanelFlingOvershootAmount; + } + ValueAnimator animator = createHeightAnimator(target, overshootAmount); + if (expand) { + if (expandBecauseOfFalsing && vel < 0) { + vel = 0; + } + mFlingAnimationUtils.apply(animator, mExpandedHeight, + target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight()); + if (vel == 0) { + animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION); + } + } else { + if (shouldUseDismissingAnimation()) { + if (vel == 0) { + animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100); + animator.setDuration(duration); + } else { + mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel, + mView.getHeight()); + } + } else { + mFlingAnimationUtilsClosing.apply( + animator, mExpandedHeight, target, vel, mView.getHeight()); + } + + // Make it shorter if we run a canned animation + if (vel == 0) { + animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor)); + } + if (mFixedDuration != NO_FIXED_DURATION) { + animator.setDuration(mFixedDuration); + } + } + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationStart(Animator animation) { + if (!mStatusBarStateController.isDozing()) { + beginJankMonitoring(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (shouldSpringBack && !mCancelled) { + // After the shade is flinged open to an overscrolled state, spring back + // the shade by reducing section padding to 0. + springBack(); + } else { + onFlingEnd(mCancelled); + } + } + }); + setAnimator(animator); + animator.start(); + } + + private void springBack() { + if (mOverExpansion == 0) { + onFlingEnd(false /* cancelled */); + return; + } + mIsSpringBackAnimation = true; + ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0); + animator.addUpdateListener( + animation -> setOverExpansionInternal((float) animation.getAnimatedValue(), + false /* isFromGesture */)); + animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + mIsSpringBackAnimation = false; + onFlingEnd(mCancelled); + } + }); + setAnimator(animator); + animator.start(); + } + + protected void onFlingEnd(boolean cancelled) { + mIsFlinging = false; + // No overshoot when the animation ends + setOverExpansionInternal(0, false /* isFromGesture */); + setAnimator(null); + mKeyguardStateController.notifyPanelFlingEnd(); + if (!cancelled) { + endJankMonitoring(); + notifyExpandingFinished(); + } else { + cancelJankMonitoring(); + } + updatePanelExpansionAndVisibility(); + } + + protected abstract boolean shouldUseDismissingAnimation(); + + public String getName() { + return mViewName; + } + + public void setExpandedHeight(float height) { + if (DEBUG) logf("setExpandedHeight(%.1f)", height); + setExpandedHeightInternal(height); + } + + protected void requestPanelHeightUpdate() { + float currentMaxPanelHeight = getMaxPanelHeight(); + + if (isFullyCollapsed()) { + return; + } + + if (currentMaxPanelHeight == mExpandedHeight) { + return; + } + + if (mTracking && !isTrackingBlocked()) { + return; + } + + if (mHeightAnimator != null && !mIsSpringBackAnimation) { + mPanelUpdateWhenAnimatorEnds = true; + return; + } + + setExpandedHeight(currentMaxPanelHeight); + } + + public void setExpandedHeightInternal(float h) { + if (isNaN(h)) { + Log.wtf(TAG, "ExpandedHeight set to NaN"); + } + mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { + if (mExpandLatencyTracking && h != 0f) { + DejankUtils.postAfterTraversal( + () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL)); + mExpandLatencyTracking = false; + } + float maxPanelHeight = getMaxPanelHeight(); + if (mHeightAnimator == null) { + // Split shade has its own overscroll logic + if (mTracking && !mInSplitShade) { + float overExpansionPixels = Math.max(0, h - maxPanelHeight); + setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); + } + mExpandedHeight = Math.min(h, maxPanelHeight); + } else { + mExpandedHeight = h; + } + + // If we are closing the panel and we are almost there due to a slow decelerating + // interpolator, abort the animation. + if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) { + mExpandedHeight = 0f; + if (mHeightAnimator != null) { + mHeightAnimator.end(); + } + } + mExpansionDragDownAmountPx = h; + mExpandedFraction = Math.min(1f, + maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + mAmbientState.setExpansionFraction(mExpandedFraction); + onHeightUpdated(mExpandedHeight); + updatePanelExpansionAndVisibility(); + }); + } + + /** + * @return true if the panel tracking should be temporarily blocked; this is used when a + * conflicting gesture (opening QS) is happening + */ + protected abstract boolean isTrackingBlocked(); + + protected void setOverExpansion(float overExpansion) { + mOverExpansion = overExpansion; + } + + /** + * Set the current overexpansion + * + * @param overExpansion the amount of overexpansion to apply + * @param isFromGesture is this amount from a gesture and needs to be rubberBanded? + */ + private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) { + if (!isFromGesture) { + mLastGesturedOverExpansion = -1; + setOverExpansion(overExpansion); + } else if (mLastGesturedOverExpansion != overExpansion) { + mLastGesturedOverExpansion = overExpansion; + final float heightForFullOvershoot = mView.getHeight() / 3.0f; + float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot); + newExpansion = Interpolators.getOvershootInterpolation(newExpansion); + setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f); + } + } + + protected abstract void onHeightUpdated(float expandedHeight); + + /** + * This returns the maximum height of the panel. Children should override this if their + * desired height is not the full height. + * + * @return the default implementation simply returns the maximum height. + */ + protected abstract int getMaxPanelHeight(); + + public void setExpandedFraction(float frac) { + setExpandedHeight(getMaxPanelHeight() * frac); + } + + public float getExpandedHeight() { + return mExpandedHeight; + } + + public float getExpandedFraction() { + return mExpandedFraction; + } + + public boolean isFullyExpanded() { + return mExpandedHeight >= getMaxPanelHeight(); + } + + public boolean isFullyCollapsed() { + return mExpandedFraction <= 0.0f; + } + + public boolean isCollapsing() { + return mClosing || mIsLaunchAnimationRunning; + } + + public boolean isFlinging() { + return mIsFlinging; + } + + public boolean isTracking() { + return mTracking; + } + + public void collapse(boolean delayed, float speedUpFactor) { + if (DEBUG) logf("collapse: " + this); + if (canPanelBeCollapsed()) { + cancelHeightAnimator(); + notifyExpandingStarted(); + + // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. + setIsClosing(true); + if (delayed) { + mNextCollapseSpeedUpFactor = speedUpFactor; + mView.postDelayed(mFlingCollapseRunnable, 120); + } else { + fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */); + } + } + } + + public boolean canPanelBeCollapsed() { + return !isFullyCollapsed() && !mTracking && !mClosing; + } + + private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, + mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); + + public void expand(final boolean animate) { + if (!isFullyCollapsed() && !isCollapsing()) { + return; + } + + mInstantExpanding = true; + mAnimateAfterExpanding = animate; + mUpdateFlingOnLayout = false; + abortAnimations(); + if (mTracking) { + onTrackingStopped(true /* expands */); // The panel is expanded after this call. + } + if (mExpanding) { + notifyExpandingFinished(); + } + updatePanelExpansionAndVisibility(); + + // Wait for window manager to pickup the change, so we know the maximum height of the panel + // then. + mView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (!mInstantExpanding) { + mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + return; + } + if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) { + mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + if (mAnimateAfterExpanding) { + notifyExpandingStarted(); + beginJankMonitoring(); + fling(0, true /* expand */); + } else { + setExpandedFraction(1f); + } + mInstantExpanding = false; + } + } + }); + + // Make sure a layout really happens. + mView.requestLayout(); + } + + public void instantCollapse() { + abortAnimations(); + setExpandedFraction(0f); + if (mExpanding) { + notifyExpandingFinished(); + } + if (mInstantExpanding) { + mInstantExpanding = false; + updatePanelExpansionAndVisibility(); + } + } + + private void abortAnimations() { + cancelHeightAnimator(); + mView.removeCallbacks(mFlingCollapseRunnable); + } + + protected abstract void onClosingFinished(); + + protected void startUnlockHintAnimation() { + + // We don't need to hint the user if an animation is already running or the user is changing + // the expansion. + if (mHeightAnimator != null || mTracking) { + return; + } + notifyExpandingStarted(); + startUnlockHintAnimationPhase1(() -> { + notifyExpandingFinished(); + onUnlockHintFinished(); + mHintAnimationRunning = false; + }); + onUnlockHintStarted(); + mHintAnimationRunning = true; + } + + protected void onUnlockHintFinished() { + mCentralSurfaces.onHintFinished(); + } + + protected void onUnlockHintStarted() { + mCentralSurfaces.onUnlockHintStarted(); + } + + public boolean isUnlockHintRunning() { + return mHintAnimationRunning; + } + + /** + * Phase 1: Move everything upwards. + */ + private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { + float target = Math.max(0, getMaxPanelHeight() - mHintDistance); + ValueAnimator animator = createHeightAnimator(target); + animator.setDuration(250); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCancelled) { + setAnimator(null); + onAnimationFinished.run(); + } else { + startUnlockHintAnimationPhase2(onAnimationFinished); + } + } + }); + animator.start(); + setAnimator(animator); + + final List<ViewPropertyAnimator> indicationAnimators = + mKeyguardBottomArea.getIndicationAreaAnimators(); + for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) { + indicationAreaAnimator + .translationY(-mHintDistance) + .setDuration(250) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .withEndAction(() -> indicationAreaAnimator + .translationY(0) + .setDuration(450) + .setInterpolator(mBounceInterpolator) + .start()) + .start(); + } + } + + private void setAnimator(ValueAnimator animator) { + mHeightAnimator = animator; + if (animator == null && mPanelUpdateWhenAnimatorEnds) { + mPanelUpdateWhenAnimatorEnds = false; + requestPanelHeightUpdate(); + } + } + + /** + * Phase 2: Bounce down. + */ + private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) { + ValueAnimator animator = createHeightAnimator(getMaxPanelHeight()); + animator.setDuration(450); + animator.setInterpolator(mBounceInterpolator); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setAnimator(null); + onAnimationFinished.run(); + updatePanelExpansionAndVisibility(); + } + }); + animator.start(); + setAnimator(animator); + } + + private ValueAnimator createHeightAnimator(float targetHeight) { + return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */); + } + + /** + * Create an animator that can also overshoot + * + * @param targetHeight the target height + * @param overshootAmount the amount of overshoot desired + */ + private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { + float startExpansion = mOverExpansion; + ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); + animator.addUpdateListener( + animation -> { + if (overshootAmount > 0.0f + // Also remove the overExpansion when collapsing + || (targetHeight == 0.0f && startExpansion != 0)) { + final float expansion = MathUtils.lerp( + startExpansion, + mPanelFlingOvershootAmount * overshootAmount, + Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + animator.getAnimatedFraction())); + setOverExpansionInternal(expansion, false /* isFromGesture */); + } + setExpandedHeightInternal((float) animation.getAnimatedValue()); + }); + return animator; + } + + /** Update the visibility of {@link NotificationPanelView} if necessary. */ + public void updateVisibility() { + mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE); + } + + /** Returns true if {@link NotificationPanelView} should be visible. */ + abstract protected boolean shouldPanelBeVisible(); + + /** + * Updates the panel expansion and {@link NotificationPanelView} visibility if necessary. + * + * TODO(b/200063118): Could public calls to this method be replaced with calls to + * {@link #updateVisibility()}? That would allow us to make this method private. + */ + public void updatePanelExpansionAndVisibility() { + mPanelExpansionStateManager.onPanelExpansionChanged( + mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + updateVisibility(); + } + + public boolean isExpanded() { + return mExpandedFraction > 0f + || mInstantExpanding + || isPanelVisibleBecauseOfHeadsUp() + || mTracking + || mHeightAnimator != null + && !mIsSpringBackAnimation; + } + + protected abstract boolean isPanelVisibleBecauseOfHeadsUp(); + + /** + * Gets called when the user performs a click anywhere in the empty area of the panel. + * + * @return whether the panel will be expanded after the action performed by this method + */ + protected boolean onEmptySpaceClick() { + if (mHintAnimationRunning) { + return true; + } + return onMiddleClicked(); + } + + protected abstract boolean onMiddleClicked(); + + protected abstract boolean isDozing(); + + public void dump(PrintWriter pw, String[] args) { + pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s" + + " tracking=%s timeAnim=%s%s " + + "touchDisabled=%s" + "]", + this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(), + mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator, + ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""), + mTouchDisabled ? "T" : "f")); + } + + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { + mHeadsUpManager = headsUpManager; + } + + public void setIsLaunchAnimationRunning(boolean running) { + mIsLaunchAnimationRunning = running; + } + + protected void setIsClosing(boolean isClosing) { + mClosing = isClosing; + } + + protected boolean isClosing() { + return mClosing; + } + + public void collapseWithDuration(int animationDuration) { + mFixedDuration = animationDuration; + collapse(false /* delayed */, 1.0f /* speedUpFactor */); + mFixedDuration = NO_FIXED_DURATION; + } + + public ViewGroup getView() { + // TODO: remove this method, or at least reduce references to it. + return mView; + } + + protected abstract OnLayoutChangeListener createLayoutChangeListener(); + + protected abstract TouchHandler createTouchHandler(); + + protected OnConfigurationChangedListener createOnConfigurationChangedListener() { + return new OnConfigurationChangedListener(); + } + + public class TouchHandler implements View.OnTouchListener { + + public boolean onInterceptTouchEvent(MotionEvent event) { + if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted + && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { + return false; + } + + /* + * If the user drags anywhere inside the panel we intercept it if the movement is + * upwards. This allows closing the shade from anywhere inside the panel. + * + * We only do this if the current content is scrolled to the bottom, + * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling + * gesture + * possible. + */ + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + boolean canCollapsePanel = canCollapsePanelOnTouch(); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mCentralSurfaces.userActivity(); + mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; + mMinExpandHeight = 0.0f; + mDownTime = mSystemClock.uptimeMillis(); + if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { + cancelHeightAnimator(); + mTouchSlopExceeded = true; + return true; + } + mInitialExpandY = y; + mInitialExpandX = x; + mTouchStartedInEmptyArea = !isInContentBounds(x, y); + mTouchSlopExceeded = mTouchSlopExceededBeforeDown; + mMotionAborted = false; + mPanelClosedOnDown = isFullyCollapsed(); + mCollapsedAndHeadsUpOnDown = false; + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mTouchAboveFalsingThreshold = false; + addMovement(event); + break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialExpandX = event.getX(newIndex); + mInitialExpandY = event.getY(newIndex); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + mVelocityTracker.clear(); + } + break; + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialExpandY; + addMovement(event); + final boolean openShadeWithoutHun = + mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; + if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown + || openShadeWithoutHun) { + float hAbs = Math.abs(h); + float touchSlop = getTouchSlop(event); + if ((h < -touchSlop + || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) + && hAbs > Math.abs(x - mInitialExpandX)) { + cancelHeightAnimator(); + startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); + return true; + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mVelocityTracker.clear(); + break; + } + return false; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mInstantExpanding) { + mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding"); + return false; + } + if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) { + mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled"); + return false; + } + if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted"); + return false; + } + + // If dragging should not expand the notifications shade, then return false. + if (!mNotificationsDragEnabled) { + if (mTracking) { + // Turn off tracking if it's on or the shade can get stuck in the down position. + onTrackingStopped(true /* expand */); + } + mShadeLog.logMotionEvent(event, "onTouch: drag not enabled"); + return false; + } + + // On expanding, single mouse click expands the panel instead of dragging. + if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.getAction() == MotionEvent.ACTION_UP) { + expand(true); + } + return true; + } + + /* + * We capture touch events here and update the expand height here in case according to + * the users fingers. This also handles multi-touch. + * + * Flinging is also enabled in order to open or close the shade. + */ + + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop(); + mIgnoreXTouchSlop = true; + } + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); + mMinExpandHeight = 0.0f; + mPanelClosedOnDown = isFullyCollapsed(); + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mMotionAborted = false; + mDownTime = mSystemClock.uptimeMillis(); + mTouchAboveFalsingThreshold = false; + mCollapsedAndHeadsUpOnDown = + isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp(); + addMovement(event); + boolean regularHeightAnimationRunning = mHeightAnimator != null + && !mHintAnimationRunning && !mIsSpringBackAnimation; + if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) { + mTouchSlopExceeded = regularHeightAnimationRunning + || mTouchSlopExceededBeforeDown; + cancelHeightAnimator(); + onTrackingStarted(); + } + if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp() + && !mCentralSurfaces.isBouncerShowing()) { + startOpening(event); + } + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mHandlingPointerUp = true; + startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); + mHandlingPointerUp = false; + } + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { + mMotionAborted = true; + endMotionEvent(event, x, y, true /* forceCancel */); + return false; + } + break; + case MotionEvent.ACTION_MOVE: + addMovement(event); + float h = y - mInitialExpandY; + + // If the panel was collapsed when touching, we only need to check for the + // y-component of the gesture, as we have no conflicting horizontal gesture. + if (Math.abs(h) > getTouchSlop(event) + && (Math.abs(h) > Math.abs(x - mInitialExpandX) + || mIgnoreXTouchSlop)) { + mTouchSlopExceeded = true; + if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { + if (mInitialOffsetOnTouch != 0f) { + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); + h = 0; + } + cancelHeightAnimator(); + onTrackingStarted(); + } + } + float newHeight = Math.max(0, h + mInitialOffsetOnTouch); + newHeight = Math.max(newHeight, mMinExpandHeight); + if (-h >= getFalsingThreshold()) { + mTouchAboveFalsingThreshold = true; + mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); + } + if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { + // Count h==0 as part of swipe-up, + // otherwise {@link NotificationStackScrollLayout} + // wrongly enables stack height updates at the start of lockscreen swipe-up + mAmbientState.setSwipingUp(h <= 0); + setExpandedHeightInternal(newHeight); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + addMovement(event); + endMotionEvent(event, x, y, false /* forceCancel */); + // mHeightAnimator is null, there is no remaining frame, ends instrumenting. + if (mHeightAnimator == null) { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + endJankMonitoring(); + } else { + cancelJankMonitoring(); + } + } + break; + } + return !mGestureWaitForTouchSlop || mTracking; + } + } + + protected abstract class OnLayoutChangeListener implements View.OnLayoutChangeListener { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, + int oldTop, int oldRight, int oldBottom) { + requestPanelHeightUpdate(); + mHasLayoutedSinceDown = true; + if (mUpdateFlingOnLayout) { + abortAnimations(); + fling(mUpdateFlingVelocity, true /* expands */); + mUpdateFlingOnLayout = false; + } + } + } + + public class OnConfigurationChangedListener implements + NotificationPanelView.OnConfigurationChangedListener { + @Override + public void onConfigurationChanged(Configuration newConfig) { + loadDimens(); + } + } + + private void beginJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withView( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + mView) + .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); + mInteractionJankMonitor.begin(builder); + } + + private void endJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().end( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + private void cancelJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().cancel( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + protected float getExpansionFraction() { + return mExpandedFraction; + } + + protected PanelExpansionStateManager getPanelExpansionStateManager() { + return mPanelExpansionStateManager; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index b9684fcb8a01..3d161d9bfa75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -24,6 +24,8 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.android.systemui.R; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; @@ -73,6 +75,7 @@ public class EmptyShadeView extends StackScrollerDecorView { } @Override + @NonNull public ExpandableViewState createExpandableViewState() { return new EmptyShadeViewState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 87f8a03098f5..47dc5c2a5513 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -19,9 +19,12 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; +import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; +import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; @@ -428,9 +431,9 @@ public class KeyguardIndicationController { if (info == null) { // Use the current user owner information if enabled. final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( - KeyguardUpdateMonitor.getCurrentUser()); + getCurrentUser()); if (ownerInfoEnabled) { - info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); + info = mLockPatternUtils.getOwnerInfo(getCurrentUser()); } } @@ -595,7 +598,7 @@ public class KeyguardIndicationController { private void updateLockScreenLogoutView() { final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled() - && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; + && getCurrentUser() != UserHandle.USER_SYSTEM; if (shouldShowLogout) { mRotateTextViewController.updateIndication( INDICATION_TYPE_LOGOUT, @@ -610,7 +613,7 @@ public class KeyguardIndicationController { if (mFalsingManager.isFalseTap(LOW_PENALTY)) { return; } - int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); + int currentUserId = getCurrentUser(); mDevicePolicyManager.logoutUser(); }) .build(), @@ -767,7 +770,7 @@ public class KeyguardIndicationController { mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE); hideBiometricMessageDelayed( mBiometricMessageFollowUp != null - ? DEFAULT_HIDE_DELAY_MS * 2 + ? IMPORTANT_MSG_MIN_DURATION * 2 : DEFAULT_HIDE_DELAY_MS ); @@ -847,7 +850,7 @@ public class KeyguardIndicationController { mTopIndicationView.setVisibility(GONE); mTopIndicationView.setText(null); mLockScreenIndicationView.setVisibility(View.VISIBLE); - updateLockScreenIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); + updateLockScreenIndications(animate, getCurrentUser()); } protected String computePowerIndication() { @@ -915,7 +918,7 @@ public class KeyguardIndicationController { public void showActionToUnlock() { if (mDozing && !mKeyguardUpdateMonitor.getUserCanSkipBouncer( - KeyguardUpdateMonitor.getCurrentUser())) { + getCurrentUser())) { return; } @@ -928,7 +931,7 @@ public class KeyguardIndicationController { } } else { final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer( - KeyguardUpdateMonitor.getCurrentUser()); + getCurrentUser()); if (canSkipBouncer) { final boolean faceAuthenticated = mKeyguardUpdateMonitor.getIsFaceAuthenticated(); final boolean udfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported(); @@ -1045,12 +1048,15 @@ public class KeyguardIndicationController { return; } - boolean showActionToUnlock = - msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; - if (biometricSourceType == BiometricSourceType.FACE - && !showActionToUnlock - && mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser()) + final boolean faceAuthSoftError = biometricSourceType == FACE + && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; + final boolean faceAuthFailed = biometricSourceType == FACE + && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed + final boolean isUnlockWithFingerprintPossible = + mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( + getCurrentUser()); + if (faceAuthSoftError + && isUnlockWithFingerprintPossible && !mCoExFaceHelpMsgIdsToShow.contains(msgId)) { if (DEBUG) { Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString @@ -1061,8 +1067,16 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { - showBiometricMessage(helpString); - } else if (showActionToUnlock) { + if (faceAuthFailed && isUnlockWithFingerprintPossible) { + showBiometricMessage( + mContext.getString(R.string.keyguard_face_failed), + mContext.getString(R.string.keyguard_suggest_fingerprint) + ); + } else { + showBiometricMessage(helpString); + } + } else if (faceAuthFailed) { + // show action to unlock mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK), TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); } else { @@ -1080,17 +1094,17 @@ public class KeyguardIndicationController { return; } - if (biometricSourceType == BiometricSourceType.FACE + if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { // suppress all face UNABLE_TO_PROCESS errors if (DEBUG) { Log.d(TAG, "skip showing FACE_ERROR_UNABLE_TO_PROCESS errString=" + errString); } - } else if (biometricSourceType == BiometricSourceType.FACE + } else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) { if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())) { + getCurrentUser())) { // no message if fingerprint is also enrolled if (DEBUG) { Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic"); @@ -1122,8 +1136,9 @@ public class KeyguardIndicationController { BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) return shouldSuppressFingerprintError(msgId, updateMonitor); - if (biometricSourceType == BiometricSourceType.FACE) + if (biometricSourceType == FACE) { return shouldSuppressFaceError(msgId, updateMonitor); + } return false; } @@ -1152,7 +1167,7 @@ public class KeyguardIndicationController { @Override public void onTrustChanged(int userId) { - if (KeyguardUpdateMonitor.getCurrentUser() != userId) { + if (getCurrentUser() != userId) { return; } updateDeviceEntryIndication(false); @@ -1172,7 +1187,7 @@ public class KeyguardIndicationController { @Override public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { - if (running && biometricSourceType == BiometricSourceType.FACE) { + if (running && biometricSourceType == FACE) { // Let's hide any previous messages when authentication starts, otherwise // multiple auth attempts would overlap. hideBiometricMessage(); @@ -1186,7 +1201,7 @@ public class KeyguardIndicationController { super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric); hideBiometricMessage(); - if (biometricSourceType == BiometricSourceType.FACE + if (biometricSourceType == FACE && !mKeyguardBypassController.canBypass()) { showActionToUnlock(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 0a616c095551..9d2750fa7b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -143,13 +143,13 @@ class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffe class CircleReveal( /** X-value of the circle center of the reveal. */ - val centerX: Float, + val centerX: Int, /** Y-value of the circle center of the reveal. */ - val centerY: Float, + val centerY: Int, /** Radius of initial state of circle reveal */ - val startRadius: Float, + val startRadius: Int, /** Radius of end state of circle reveal */ - val endRadius: Float + val endRadius: Int ) : LightRevealEffect { override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { // reveal amount updates already have an interpolator, so we intentionally use the @@ -350,7 +350,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, * This method does not call [invalidate] - you should do so once you're done changing * properties. */ - public fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) { + fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) { revealGradientWidth = right - left revealGradientHeight = bottom - top @@ -387,4 +387,4 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha), PorterDuff.Mode.MULTIPLY) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt index 2ca1bebfcf9f..7b49ecdcd981 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt @@ -1,7 +1,6 @@ package com.android.systemui.statusbar import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import javax.inject.Inject @@ -12,14 +11,12 @@ import javax.inject.Inject */ @SysUISingleton class NotificationInteractionTracker @Inject constructor( - private val clicker: NotificationClickNotifier, - private val entryManager: NotificationEntryManager + clicker: NotificationClickNotifier, ) : NotifCollectionListener, NotificationInteractionListener { private val interactions = mutableMapOf<String, Boolean>() init { clicker.addNotificationInteractionListener(this) - entryManager.addCollectionListener(this) } fun hasUserInteractedWith(key: String): Boolean { @@ -38,5 +35,3 @@ class NotificationInteractionTracker @Inject constructor( interactions[key] = true } } - -private const val TAG = "NotificationInteractionTracker" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 221428747558..cea3debe6379 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -29,6 +29,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; @@ -153,6 +155,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } @Override + @NonNull public ExpandableViewState createExpandableViewState() { return new ShelfState(); } 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 8cb18a06057e..59022c0ffbf2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -50,7 +50,6 @@ import android.util.Pair; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.CoreStartable; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -76,20 +75,22 @@ public class InstantAppNotifier extends CoreStartable private final Executor mUiBgExecutor; private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>(); private final CommandQueue mCommandQueue; - private KeyguardStateController mKeyguardStateController; + private final KeyguardStateController mKeyguardStateController; @Inject - public InstantAppNotifier(Context context, CommandQueue commandQueue, - @UiBackground Executor uiBgExecutor) { + public InstantAppNotifier( + Context context, + CommandQueue commandQueue, + @UiBackground Executor uiBgExecutor, + KeyguardStateController keyguardStateController) { super(context); mCommandQueue = commandQueue; mUiBgExecutor = uiBgExecutor; + mKeyguardStateController = keyguardStateController; } @Override public void start() { - mKeyguardStateController = Dependency.get(KeyguardStateController.class); - // listen for user / profile change. try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java deleted file mode 100644 index c9c6f28b5b27..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification; - -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; - -import java.util.Objects; - -/** - * Root controller for the list of notifications in the shade. - * - * TODO: Much of the code in NotificationPresenter should eventually move in here. It will proxy - * domain-specific behavior (ARC, etc) to subcontrollers. - */ -public class NotificationListController { - private final NotificationEntryManager mEntryManager; - private final NotificationListContainer mListContainer; - private final DeviceProvisionedController mDeviceProvisionedController; - - public NotificationListController( - NotificationEntryManager entryManager, - NotificationListContainer listContainer, - DeviceProvisionedController deviceProvisionedController) { - mEntryManager = Objects.requireNonNull(entryManager); - mListContainer = Objects.requireNonNull(listContainer); - mDeviceProvisionedController = Objects.requireNonNull(deviceProvisionedController); - } - - /** - * Causes the controller to register listeners on its dependencies. This method must be called - * before the controller is ready to perform its duties. - */ - public void bind() { - mEntryManager.addNotificationEntryListener(mEntryListener); - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - } - - @SuppressWarnings("FieldCanBeLocal") - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - @Override - public void onEntryRemoved( - NotificationEntry entry, - NotificationVisibility visibility, - boolean removedByUser, - int reason) { - mListContainer.cleanUpViewStateForEntry(entry); - } - }; - - // TODO: (b/145659174) remove after moving to NewNotifPipeline. Replaced by - // DeviceProvisionedCoordinator - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - mEntryManager.updateNotifications("device provisioned changed"); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index dbf4810b4fd7..126a986ee5f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -18,10 +18,8 @@ package com.android.systemui.statusbar.notification import android.animation.ObjectAnimator import android.util.FloatProperty -import com.android.systemui.Dumpable import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -34,20 +32,17 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener -import java.io.PrintWriter import javax.inject.Inject import kotlin.math.min @SysUISingleton class NotificationWakeUpCoordinator @Inject constructor( - dumpManager: DumpManager, private val mHeadsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController, private val bypassController: KeyguardBypassController, private val dozeParameters: DozeParameters, private val screenOffAnimationController: ScreenOffAnimationController -) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener, - Dumpable { +) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener { private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>( "notificationVisibility") { @@ -65,7 +60,6 @@ class NotificationWakeUpCoordinator @Inject constructor( private var mLinearDozeAmount: Float = 0.0f private var mDozeAmount: Float = 0.0f - private var mDozeAmountSource: String = "init" private var mNotificationVisibleAmount = 0.0f private var mNotificationsVisible = false private var mNotificationsVisibleForExpansion = false @@ -148,7 +142,6 @@ class NotificationWakeUpCoordinator @Inject constructor( } init { - dumpManager.registerDumpable(this) mHeadsUpManager.addListener(this) statusBarStateController.addCallback(this) addListener(object : WakeUpListener { @@ -255,14 +248,13 @@ class NotificationWakeUpCoordinator @Inject constructor( // Let's notify the scroller that an animation started notifyAnimationStart(mLinearDozeAmount == 1.0f) } - setDozeAmount(linear, eased, source = "StatusBar") + setDozeAmount(linear, eased) } - fun setDozeAmount(linear: Float, eased: Float, source: String) { + fun setDozeAmount(linear: Float, eased: Float) { val changed = linear != mLinearDozeAmount mLinearDozeAmount = linear mDozeAmount = eased - mDozeAmountSource = source mStackScrollerController.setDozeAmount(mDozeAmount) updateHideAmount() if (changed && linear == 0.0f) { @@ -279,7 +271,7 @@ class NotificationWakeUpCoordinator @Inject constructor( // undefined state, so it's an indication that we should do state cleanup. We override // the doze amount to 0f (not dozing) so that the notifications are no longer hidden. // See: UnlockedScreenOffAnimationController.onFinishedWakingUp() - setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)") + setDozeAmount(0f, 0f) } if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) { @@ -319,11 +311,12 @@ class NotificationWakeUpCoordinator @Inject constructor( */ private fun overrideDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { - if (statusBarStateController.state == StatusBarState.KEYGUARD) { - setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)") - } else { - setDozeAmount(0f, 0f, source = "Override: bypass (shade)") + var amount = 1.0f + if (statusBarStateController.state == StatusBarState.SHADE || + statusBarStateController.state == StatusBarState.SHADE_LOCKED) { + amount = 0.0f } + setDozeAmount(amount, amount) return true } return false @@ -339,7 +332,7 @@ class NotificationWakeUpCoordinator @Inject constructor( */ private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean { if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) { - setDozeAmount(1f, 1f, source = "Override: animating screen off") + setDozeAmount(1f, 1f) return true } @@ -433,24 +426,4 @@ class NotificationWakeUpCoordinator @Inject constructor( */ @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {} } - - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("mLinearDozeAmount: $mLinearDozeAmount") - pw.println("mDozeAmount: $mDozeAmount") - pw.println("mDozeAmountSource: $mDozeAmountSource") - pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount") - pw.println("mNotificationsVisible: $mNotificationsVisible") - pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion") - pw.println("mVisibilityAmount: $mVisibilityAmount") - pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount") - pw.println("pulseExpanding: $pulseExpanding") - pw.println("state: ${StatusBarState.toString(state)}") - pw.println("fullyAwake: $fullyAwake") - pw.println("wakingUp: $wakingUp") - pw.println("willWakeUp: $willWakeUp") - pw.println("collapsedEnoughToHide: $collapsedEnoughToHide") - pw.println("pulsing: $pulsing") - pw.println("notificationsFullyHidden: $notificationsFullyHidden") - pw.println("canShowPulsingHuns: $canShowPulsingHuns") - } -} +}
\ No newline at end of file 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 385fbb5ccdee..be9f133c9aeb 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 @@ -27,20 +27,20 @@ import com.android.systemui.statusbar.notification.AnimatedImageNotificationMana 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.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.TargetSdkResolver 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.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.logging.NotificationLogger import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy import java.io.PrintWriter @@ -65,7 +65,6 @@ class NotificationsControllerImpl @Inject constructor( private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>, private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val notificationLogger: NotificationLogger, - private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, private val notificationsMediaManager: NotificationMediaManager, private val headsUpViewBinder: HeadsUpViewBinder, @@ -85,12 +84,11 @@ class NotificationsControllerImpl @Inject constructor( ) { notificationListener.registerAsSystemService() - val listController = - NotificationListController( - entryManager, - listContainer, - deviceProvisionedController) - listController.bind() + notifPipeline.get().addCollectionListener(object : NotifCollectionListener { + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + listContainer.cleanUpViewStateForEntry(entry) + } + }) notificationRowBinder.setNotificationClicker( clickerBuilder.build( 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 855390d75ff8..8574f872ad9c 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 @@ -25,8 +25,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -68,6 +66,9 @@ import android.widget.Chronometer; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -3247,6 +3248,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + @NonNull public ExpandableViewState createExpandableViewState() { return new NotificationViewState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 8f73b802271d..1e09b8a37645 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -29,6 +29,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.Dumpable; @@ -66,7 +67,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { protected float mContentTransformationAmount; protected boolean mIsLastChild; protected int mContentShift; - private final ExpandableViewState mViewState; + @NonNull private final ExpandableViewState mViewState; private float mContentTranslation; protected boolean mLastInSection; protected boolean mFirstInSection; @@ -610,6 +611,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { public void setActualHeightAnimating(boolean animating) {} + @NonNull protected ExpandableViewState createExpandableViewState() { return new ExpandableViewState(); } @@ -642,7 +644,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { return mViewState; } - @Nullable public ExpandableViewState getViewState() { + /** + * Get the {@link ExpandableViewState} associated with the view. + * + * @return the ExpandableView's view state. + */ + @NonNull public ExpandableViewState getViewState() { return mViewState; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index e43ecf70a308..49dc6550a51f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -23,6 +23,8 @@ import android.util.AttributeSet; import android.util.IndentingPrintWriter; import android.view.View; +import androidx.annotation.NonNull; + import com.android.systemui.R; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -142,6 +144,7 @@ public class FooterView extends StackScrollerDecorView { } @Override + @NonNull public ExpandableViewState createExpandableViewState() { return new FooterViewState(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java deleted file mode 100644 index 6abfee9818bc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification.row; - -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; - -import android.content.Context; -import android.metrics.LogMaker; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; -import com.android.systemui.statusbar.notification.dagger.NotificationsModule; -import com.android.systemui.statusbar.notification.logging.NotificationCounters; -import com.android.systemui.util.Compile; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Manager for the notification blocking helper - tracks and helps create the blocking helper - * affordance. - */ -public class NotificationBlockingHelperManager { - /** Enables debug logging and always makes the blocking helper show up after a dismiss. */ - private static final String TAG = "BlockingHelper"; - private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); - private static final boolean DEBUG_ALWAYS_SHOW = false; - - private final Context mContext; - private final NotificationGutsManager mNotificationGutsManager; - private final NotificationEntryManager mNotificationEntryManager; - private final MetricsLogger mMetricsLogger; - private final GroupMembershipManager mGroupMembershipManager; - /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */ - private ExpandableNotificationRow mBlockingHelperRow; - private Set<String> mNonBlockablePkgs; - - /** - * Whether the notification shade/stack is expanded - used to determine blocking helper - * eligibility. - */ - private boolean mIsShadeExpanded; - - /** - * Injected constructor. See {@link NotificationsModule}. - */ - public NotificationBlockingHelperManager( - Context context, - NotificationGutsManager notificationGutsManager, - NotificationEntryManager notificationEntryManager, - MetricsLogger metricsLogger, - GroupMembershipManager groupMembershipManager) { - mContext = context; - mNotificationGutsManager = notificationGutsManager; - mNotificationEntryManager = notificationEntryManager; - mMetricsLogger = metricsLogger; - mNonBlockablePkgs = new HashSet<>(); - Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray( - com.android.internal.R.array.config_nonBlockableNotificationPackages)); - mGroupMembershipManager = groupMembershipManager; - } - - /** - * Potentially shows the blocking helper, represented via the {@link NotificationInfo} menu - * item, in the current row if user sentiment is negative. - * - * @param row row to render the blocking helper in - * @param menuRow menu used to generate the {@link NotificationInfo} view that houses the - * blocking helper UI - * @return whether we're showing a blocking helper in the given notification row - */ - boolean perhapsShowBlockingHelper( - ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow) { - // We only show the blocking helper if: - // - User sentiment is negative (DEBUG flag can bypass) - // - The notification shade is fully expanded (guarantees we're not touching a HUN). - // - The row is blockable (i.e. not non-blockable) - // - The dismissed row is a valid group (>1 or 0 children from the same channel) - // or the only child in the group - final NotificationEntry entry = row.getEntry(); - if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG_ALWAYS_SHOW) - && mIsShadeExpanded - && !row.getIsNonblockable() - && ((!row.isChildInGroup() || mGroupMembershipManager.isOnlyChildInGroup(entry)) - && row.getNumUniqueChannels() <= 1)) { - // Dismiss any current blocking helper before continuing forward (only one can be shown - // at a given time). - dismissCurrentBlockingHelper(); - - if (DEBUG) { - Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper"); - } - - // Enable blocking helper on the row before moving forward so everything in the guts is - // correctly prepped. - mBlockingHelperRow = row; - mBlockingHelperRow.setBlockingHelperShowing(true); - - // Log triggering of blocking helper by the system. This log line - // should be emitted before the "display" log line. - mMetricsLogger.write( - getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM)); - - // We don't care about the touch origin (x, y) since we're opening guts without any - // explicit user interaction. - mNotificationGutsManager.openGuts( - mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext)); - - mMetricsLogger.count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1); - return true; - } - return false; - } - - /** - * Dismiss the currently showing blocking helper, if any, through a notification update. - * - * @return whether the blocking helper was dismissed - */ - boolean dismissCurrentBlockingHelper() { - if (!isBlockingHelperRowNull()) { - if (DEBUG) { - Log.d(TAG, "Manager.dismissCurrentBlockingHelper: Dismissing current helper"); - } - if (!mBlockingHelperRow.isBlockingHelperShowing()) { - Log.e(TAG, "Manager.dismissCurrentBlockingHelper: " - + "Non-null row is not showing a blocking helper"); - } - - mBlockingHelperRow.setBlockingHelperShowing(false); - if (mBlockingHelperRow.isAttachedToWindow()) { - mNotificationEntryManager.updateNotifications("dismissCurrentBlockingHelper"); - } - mBlockingHelperRow = null; - return true; - } - return false; - } - - /** - * Update the expansion status of the notification shade/stack. - * - * @param expandedHeight how much the shade is expanded ({code 0} indicating it's collapsed) - */ - public void setNotificationShadeExpanded(float expandedHeight) { - mIsShadeExpanded = expandedHeight > 0.0f; - } - - /** - * Returns whether the given package name is in the list of non-blockable packages. - */ - public boolean isNonblockable(String packageName, String channelName) { - return mNonBlockablePkgs.contains(packageName) - || mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName)); - } - - private LogMaker getLogMaker() { - return mBlockingHelperRow.getEntry().getSbn() - .getLogMaker() - .setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER); - } - - // Format must stay in sync with frameworks/base/core/res/res/values/config.xml - // config_nonBlockableNotificationPackages - private String makeChannelKey(String pkg, String channel) { - return pkg + ":" + channel; - } - - @VisibleForTesting - boolean isBlockingHelperRowNull() { - return mBlockingHelperRow == null; - } - - @VisibleForTesting - void setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest) { - mBlockingHelperRow = blockingHelperRowForTest; - } - - @VisibleForTesting - void setNonBlockablePkgs(String[] pkgsAndChannels) { - mNonBlockablePkgs = new HashSet<>(); - Collections.addAll(mNonBlockablePkgs, pkgsAndChannels); - } -} 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 cc87499d39cb..1fb265fea0b1 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 @@ -4460,8 +4460,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void updateVisibility() { - boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard(); - setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); + mController.updateVisibility(!mAmbientState.isFullyHidden() || !onKeyguard()); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4526,17 +4525,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - void updateEmptyShadeView(boolean visible, boolean notifVisibleInShade) { + void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); int oldTextRes = mEmptyShadeView.getTextResource(); - int newTextRes = notifVisibleInShade + int newTextRes = areNotificationsHiddenInShade ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text; if (oldTextRes != newTextRes) { mEmptyShadeView.setText(newTextRes); } } + public boolean isEmptyShadeViewVisible() { + return mEmptyShadeView.isVisible(); + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { if (mFooterView == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e6eceb555897..ec1e6207ef8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -177,7 +177,6 @@ public class NotificationStackScrollLayoutController { private NotificationStackScrollLayout mView; private boolean mFadeNotificationsOnDismiss; private NotificationSwipeHelper mSwipeHelper; - private boolean mShowEmptyShadeView; @Nullable private Boolean mHistoryEnabled; private int mBarState; private HeadsUpAppearanceController mHeadsUpAppearanceController; @@ -1173,8 +1172,21 @@ public class NotificationStackScrollLayoutController { } /** - * Update whether we should show the empty shade view (no notifications in the shade). - * If so, send the update to our view. + * Set the visibility of the view, and propagate it to specific children. + * + * @param visible either the view is visible or not. + */ + public void updateVisibility(boolean visible) { + mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + + if (mView.getVisibility() == View.VISIBLE) { + // Synchronize EmptyShadeView visibility with the parent container. + updateShowEmptyShadeView(); + } + } + + /** + * Update whether we should show the empty shade view ("no notifications" in the shade). * * When in split mode, notifications are always visible regardless of the state of the * QuickSettings panel. That being the case, empty view is always shown if the other conditions @@ -1182,18 +1194,31 @@ public class NotificationStackScrollLayoutController { */ public void updateShowEmptyShadeView() { Trace.beginSection("NSSLC.updateShowEmptyShadeView"); - mShowEmptyShadeView = mStatusBarStateController.getCurrentOrUpcomingState() != KEYGUARD + + final boolean shouldShow = getVisibleNotificationCount() == 0 && !mView.isQsFullScreen() - && getVisibleNotificationCount() == 0; + // Hide empty shade view when in transition to Keyguard. + // That avoids "No Notifications" to blink when transitioning to AOD. + // For more details, see: b/228790482 + && !isInTransitionToKeyguard(); + + mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); - mView.updateEmptyShadeView( - mShowEmptyShadeView, - mZenModeController.areNotificationsHiddenInShade()); Trace.endSection(); } + /** + * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD + * and false otherwise. + */ + private boolean isInTransitionToKeyguard() { + final int currentState = mStatusBarStateController.getState(); + final int upcomingState = mStatusBarStateController.getCurrentOrUpcomingState(); + return (currentState != upcomingState && upcomingState == KEYGUARD); + } + public boolean isShowingEmptyShadeView() { - return mShowEmptyShadeView; + return mView.isEmptyShadeViewVisible(); } public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 6d513d0da5c1..353355bef7bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -120,11 +120,64 @@ public class StackScrollAlgorithm { updateClipping(algorithmState, ambientState); updateSpeedBumpState(algorithmState, speedBumpIndex); updateShelfState(algorithmState, ambientState); + updateAlphaState(algorithmState, ambientState); getNotificationChildrenStates(algorithmState, ambientState); } + private void updateAlphaState(StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { + for (ExpandableView view : algorithmState.visibleChildren) { + final ViewState viewState = view.getViewState(); + + final boolean isHunGoingToShade = ambientState.isShadeExpanded() + && view == ambientState.getTrackedHeadsUpRow(); + + if (isHunGoingToShade) { + // Keep 100% opacity for heads up notification going to shade. + viewState.alpha = 1f; + } else if (ambientState.isOnKeyguard()) { + // Adjust alpha for wakeup to lockscreen. + viewState.alpha = 1f - ambientState.getHideAmount(); + } else if (ambientState.isExpansionChanging()) { + // Adjust alpha for shade open & close. + float expansion = ambientState.getExpansionFraction(); + viewState.alpha = ambientState.isBouncerInTransit() + ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion) + : ShadeInterpolation.getContentAlpha(expansion); + } + + // For EmptyShadeView if on keyguard, we need to control the alpha to create + // a nice transition when the user is dragging down the notification panel. + if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) { + final float fractionToShade = ambientState.getFractionToShade(); + viewState.alpha = ShadeInterpolation.getContentAlpha(fractionToShade); + } + + NotificationShelf shelf = ambientState.getShelf(); + if (shelf != null) { + final ViewState shelfState = shelf.getViewState(); + + // After the shelf has updated its yTranslation, explicitly set alpha=0 for view + // below shelf to skip rendering them in the hardware layer. We do not set them + // invisible because that runs invalidate & onDraw when these views return onscreen, + // which is more expensive. + if (shelfState.hidden) { + // When the shelf is hidden, it won't clip views, so we don't hide rows + return; + } + + final float shelfTop = shelfState.yTranslation; + final float viewTop = viewState.yTranslation; + if (viewTop >= shelfTop) { + viewState.alpha = 0; + } + } + } + } + /** * How expanded or collapsed notifications are when pulling down the shade. + * * @param ambientState Current ambient state. * @return 0 when fully collapsed, 1 when expanded. */ @@ -208,22 +261,6 @@ public class StackScrollAlgorithm { } shelf.updateState(algorithmState, ambientState); - - // After the shelf has updated its yTranslation, explicitly set alpha=0 for view below shelf - // to skip rendering them in the hardware layer. We do not set them invisible because that - // runs invalidate & onDraw when these views return onscreen, which is more expensive. - if (shelf.getViewState().hidden) { - // When the shelf is hidden, it won't clip views, so we don't hide rows - return; - } - final float shelfTop = shelf.getViewState().yTranslation; - - for (ExpandableView view : algorithmState.visibleChildren) { - final float viewTop = view.getViewState().yTranslation; - if (viewTop >= shelfTop) { - view.getViewState().alpha = 0; - } - } } private void updateClipping(StackScrollAlgorithmState algorithmState, @@ -473,21 +510,6 @@ public class StackScrollAlgorithm { ExpandableViewState viewState = view.getViewState(); viewState.location = ExpandableViewState.LOCATION_UNKNOWN; - final boolean isHunGoingToShade = ambientState.isShadeExpanded() - && view == ambientState.getTrackedHeadsUpRow(); - if (isHunGoingToShade) { - // Keep 100% opacity for heads up notification going to shade. - } else if (ambientState.isOnKeyguard()) { - // Adjust alpha for wakeup to lockscreen. - viewState.alpha = 1f - ambientState.getHideAmount(); - } else if (ambientState.isExpansionChanging()) { - // Adjust alpha for shade open & close. - float expansion = ambientState.getExpansionFraction(); - viewState.alpha = ambientState.isBouncerInTransit() - ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion) - : ShadeInterpolation.getContentAlpha(expansion); - } - final float expansionFraction = getExpansionFractionWithoutShelf( algorithmState, ambientState); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 747c4debb771..52a45d6785ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -55,7 +55,6 @@ import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; -import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -326,12 +325,12 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mNotificationPanelViewController.expand(true /* animate */); mNotificationStackScrollLayoutController.setWillExpand(true); mHeadsUpManager.unpinAll(true /* userUnpinned */); - mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1); + mMetricsLogger.count("panel_open", 1); } else if (!mNotificationPanelViewController.isInSettings() && !mNotificationPanelViewController.isExpanding()) { mNotificationPanelViewController.flingSettings(0 /* velocity */, - NotificationPanelView.FLING_EXPAND); - mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1); + NotificationPanelViewController.FLING_EXPAND); + mMetricsLogger.count("panel_open_qs", 1); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index ed186ab6a10b..8273d5737c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -33,7 +33,6 @@ import android.widget.LinearLayout.LayoutParams; import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoModeCommandReceiver; @@ -137,11 +136,12 @@ public interface StatusBarIconController { LinearLayout linearLayout, FeatureFlags featureFlags, StatusBarPipelineFlags statusBarPipelineFlags, - Provider<WifiViewModel> wifiViewModelProvider) { + Provider<WifiViewModel> wifiViewModelProvider, + DarkIconDispatcher darkIconDispatcher) { super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); mIconHPadding = mContext.getResources().getDimensionPixelSize( R.dimen.status_bar_icon_padding); - mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); + mDarkIconDispatcher = darkIconDispatcher; } @Override @@ -198,20 +198,24 @@ public interface StatusBarIconController { private final FeatureFlags mFeatureFlags; private final StatusBarPipelineFlags mStatusBarPipelineFlags; private final Provider<WifiViewModel> mWifiViewModelProvider; + private final DarkIconDispatcher mDarkIconDispatcher; @Inject public Factory( FeatureFlags featureFlags, StatusBarPipelineFlags statusBarPipelineFlags, - Provider<WifiViewModel> wifiViewModelProvider) { + Provider<WifiViewModel> wifiViewModelProvider, + DarkIconDispatcher darkIconDispatcher) { mFeatureFlags = featureFlags; mStatusBarPipelineFlags = statusBarPipelineFlags; mWifiViewModelProvider = wifiViewModelProvider; + mDarkIconDispatcher = darkIconDispatcher; } public DarkIconManager create(LinearLayout group) { return new DarkIconManager( - group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider); + group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider, + mDarkIconDispatcher); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 77654273bde4..103f3fc21f91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -46,7 +46,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn /** * Provides data related to the wifi state. @@ -118,12 +118,19 @@ class WifiRepositoryImpl @Inject constructor( } } - trySend(WIFI_NETWORK_DEFAULT) connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } } - .shareIn(scope, started = SharingStarted.WhileSubscribed()) + // There will be multiple wifi icons in different places that will frequently + // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that + // new subscribes will get the latest value immediately upon subscription. Otherwise, the + // views could show stale data. See b/244173280. + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = WIFI_NETWORK_DEFAULT + ) override val wifiActivity: Flow<WifiActivityModel> = if (wifiManager == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 169347a5ac1a..163060814545 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.UserAvatarView; +import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -79,7 +80,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> @VisibleForTesting UserAvatarView mUserAvatarView; private View mUserAvatarViewWithBackground; - UserSwitcherController.UserRecord mCurrentUser; + UserRecord mCurrentUser; private boolean mIsKeyguardShowing; // State info for the user switch and keyguard @@ -269,10 +270,10 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> * @return true if the current user has changed */ private boolean updateCurrentUser() { - UserSwitcherController.UserRecord previousUser = mCurrentUser; + UserRecord previousUser = mCurrentUser; mCurrentUser = null; for (int i = 0; i < mAdapter.getCount(); i++) { - UserSwitcherController.UserRecord r = mAdapter.getItem(i); + UserRecord r = mAdapter.getItem(i); if (r.isCurrent) { mCurrentUser = r; return !mCurrentUser.equals(previousUser); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index 03ab888d1253..e2f5734cb4f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.ViewController; import java.util.ArrayList; @@ -287,8 +288,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView) mAdapter.getView(i, oldView, mListView); - UserSwitcherController.UserRecord userTag = - (UserSwitcherController.UserRecord) newView.getTag(); + UserRecord userTag = + (UserRecord) newView.getTag(); if (userTag.isCurrent) { if (i != 0) { Log.w(TAG, "Current user is not the first view in the list"); @@ -443,7 +444,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private View mCurrentUserView; // List of users where the first entry is always the current user - private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>(); + private ArrayList<UserRecord> mUsersOrdered = new ArrayList<>(); KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, UserSwitcherController controller, @@ -464,10 +465,10 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } void refreshUserOrder() { - ArrayList<UserSwitcherController.UserRecord> users = super.getUsers(); + ArrayList<UserRecord> users = super.getUsers(); mUsersOrdered = new ArrayList<>(users.size()); for (int i = 0; i < users.size(); i++) { - UserSwitcherController.UserRecord record = users.get(i); + UserRecord record = users.get(i); if (record.isCurrent) { mUsersOrdered.add(0, record); } else { @@ -477,19 +478,19 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } @Override - protected ArrayList<UserSwitcherController.UserRecord> getUsers() { + protected ArrayList<UserRecord> getUsers() { return mUsersOrdered; } @Override public View getView(int position, View convertView, ViewGroup parent) { - UserSwitcherController.UserRecord item = getItem(position); + UserRecord item = getItem(position); return createUserDetailItemView(convertView, parent, item); } KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { if (!(convertView instanceof KeyguardUserDetailItemView) - || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { + || !(convertView.getTag() instanceof UserRecord)) { convertView = mLayoutInflater.inflate( R.layout.keyguard_user_switcher_item, parent, false); } @@ -497,7 +498,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS } KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, - UserSwitcherController.UserRecord item) { + UserRecord item) { KeyguardUserDetailItemView v = convertOrInflate(convertView, parent); v.setOnClickListener(this); @@ -513,7 +514,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS v.bind(name, drawable, item.info.id); } v.setActivated(item.isCurrent); - v.setDisabledByAdmin(item.isDisabledByAdmin); + v.setDisabledByAdmin(mController.isDisabledByAdmin(item)); v.setEnabled(item.isSwitchToEnabled); v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); @@ -524,7 +525,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS return v; } - private Drawable getDrawable(UserSwitcherController.UserRecord item) { + private Drawable getDrawable(UserRecord item) { Drawable drawable; if (item.isCurrent && item.isGuest) { drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); @@ -547,7 +548,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS @Override public void onClick(View v) { - UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag(); + UserRecord user = (UserRecord) v.getTag(); if (mKeyguardUserSwitcherController.isListAnimating()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 836d57131fac..e2d16015235e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -45,6 +45,7 @@ import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyCallback; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -54,6 +55,7 @@ import android.widget.BaseAdapter; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.collection.SimpleArrayMap; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; @@ -83,6 +85,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; +import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -138,6 +141,9 @@ public class UserSwitcherController implements Dumpable { private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private final DialogLaunchAnimator mDialogLaunchAnimator; + private final SimpleArrayMap<UserRecord, EnforcedAdmin> mEnforcedAdminByUserRecord = + new SimpleArrayMap<>(); + private final ArraySet<UserRecord> mDisabledByAdmin = new ArraySet<>(); private ArrayList<UserRecord> mUsers = new ArrayList<>(); @VisibleForTesting @@ -975,6 +981,21 @@ public class UserSwitcherController implements Dumpable { return mKeyguardStateController; } + /** + * Returns the {@link EnforcedAdmin} for the given record, or {@code null} if there isn't one. + */ + @Nullable + public EnforcedAdmin getEnforcedAdmin(UserRecord record) { + return mEnforcedAdminByUserRecord.get(record); + } + + /** + * Returns {@code true} if the given record is disabled by the admin; {@code false} otherwise. + */ + public boolean isDisabledByAdmin(UserRecord record) { + return mDisabledByAdmin.contains(record); + } + public static abstract class BaseUserAdapter extends BaseAdapter { final UserSwitcherController mController; @@ -1106,11 +1127,11 @@ public class UserSwitcherController implements Dumpable { UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId()); if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) { - record.isDisabledByAdmin = true; - record.enforcedAdmin = admin; + mDisabledByAdmin.add(record); + mEnforcedAdminByUserRecord.put(record, admin); } else { - record.isDisabledByAdmin = false; - record.enforcedAdmin = null; + mDisabledByAdmin.remove(record); + mEnforcedAdminByUserRecord.put(record, null); } } @@ -1152,74 +1173,6 @@ public class UserSwitcherController implements Dumpable { } } - public static final class UserRecord { - public final UserInfo info; - public final Bitmap picture; - public final boolean isGuest; - public final boolean isCurrent; - public final boolean isAddUser; - public final boolean isAddSupervisedUser; - /** If true, the record is only visible to the owner and only when unlocked. */ - public final boolean isRestricted; - public boolean isDisabledByAdmin; - public EnforcedAdmin enforcedAdmin; - public boolean isSwitchToEnabled; - - public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, - boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled, - boolean isAddSupervisedUser) { - this.info = info; - this.picture = picture; - this.isGuest = isGuest; - this.isCurrent = isCurrent; - this.isAddUser = isAddUser; - this.isRestricted = isRestricted; - this.isSwitchToEnabled = isSwitchToEnabled; - this.isAddSupervisedUser = isAddSupervisedUser; - } - - public UserRecord copyWithIsCurrent(boolean _isCurrent) { - return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted, - isSwitchToEnabled, isAddSupervisedUser); - } - - public int resolveId() { - if (isGuest || info == null) { - return UserHandle.USER_NULL; - } - return info.id; - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("UserRecord("); - if (info != null) { - sb.append("name=\"").append(info.name).append("\" id=").append(info.id); - } else { - if (isGuest) { - sb.append("<add guest placeholder>"); - } else if (isAddUser) { - sb.append("<add user placeholder>"); - } - } - if (isGuest) sb.append(" <isGuest>"); - if (isAddUser) sb.append(" <isAddUser>"); - if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>"); - if (isCurrent) sb.append(" <isCurrent>"); - if (picture != null) sb.append(" <hasPicture>"); - if (isRestricted) sb.append(" <isRestricted>"); - if (isDisabledByAdmin) { - sb.append(" <isDisabledByAdmin>"); - sb.append(" enforcedAdmin=").append(enforcedAdmin); - } - if (isSwitchToEnabled) { - sb.append(" <isSwitchToEnabled>"); - } - sb.append(')'); - return sb.toString(); - } - } - private final KeyguardStateController.Callback mCallback = new KeyguardStateController.Callback() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index 80c55c0bad07..ff0f0d48a7c5 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -51,7 +51,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA -import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord +import com.android.systemui.user.data.source.UserRecord import javax.inject.Inject import kotlin.math.ceil @@ -81,16 +81,17 @@ class UserSwitcherActivity @Inject constructor( } } // When the add users options become available, insert another option to manage users - private val manageUserRecord = UserRecord( - null /* info */, - null /* picture */, - false /* isGuest */, - false /* isCurrent */, - false /* isAddUser */, - false /* isRestricted */, - false /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) + private val manageUserRecord = + UserRecord( + null /* info */, + null /* picture */, + false /* isGuest */, + false /* isCurrent */, + false /* isAddUser */, + false /* isRestricted */, + false /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) private val adapter = object : BaseUserAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt new file mode 100644 index 000000000000..6ab6d7d7891a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.android.systemui.user.data.source + +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.os.UserHandle + +/** + * Encapsulates raw data for a user or an option item related to managing users on the device. + */ +data class UserRecord( + /** Relevant user information. If `null`, this record is not a user but an option item. */ + @JvmField + val info: UserInfo?, + /** An image representing the user. */ + @JvmField + val picture: Bitmap?, + /** Whether this record represents an option to switch to a guest user. */ + @JvmField + val isGuest: Boolean, + /** Whether this record represents the currently-selected user. */ + @JvmField + val isCurrent: Boolean, + /** Whether this record represents an option to add another user to the device. */ + @JvmField + val isAddUser: Boolean, + /** If true, the record is only visible to the owner and only when unlocked. */ + @JvmField + val isRestricted: Boolean, + /** Whether it is possible to switch to this user. */ + @JvmField + val isSwitchToEnabled: Boolean, + /** Whether this record represents an option to add another supervised user to the device. */ + @JvmField + val isAddSupervisedUser: Boolean, +) { + /** + * Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. + */ + fun copyWithIsCurrent(isCurrent: Boolean): UserRecord { + return copy(isCurrent = isCurrent) + } + + /** + * Returns the user ID for the user represented by this instance or [UserHandle.USER_NULL] if + * this instance if a guest or does not represent a user (represents an option item). + */ + fun resolveId(): Int { + return if (isGuest || info == null) { + UserHandle.USER_NULL + } else { + info.id + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/src/com/android/systemui/volume/OWNERS new file mode 100644 index 000000000000..e627d610dc0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/OWNERS @@ -0,0 +1,4 @@ +asc@google.com # send reviews here + +juliacr@google.com +tsuji@google.com diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index f2ac0c7a7736..28e99da49496 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -57,7 +57,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord; +import com.android.systemui.user.data.source.UserRecord; import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 64a7986d05b1..df10dfe9f160 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -16,7 +16,6 @@ package com.android.systemui; import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; -import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; @@ -36,6 +35,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -49,7 +49,6 @@ import android.annotation.IdRes; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Insets; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -60,7 +59,6 @@ import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.util.RotationUtils; import android.util.Size; import android.view.Display; import android.view.DisplayCutout; @@ -80,6 +78,8 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.decor.CornerDecorProvider; +import com.android.systemui.decor.CutoutDecorProviderFactory; +import com.android.systemui.decor.CutoutDecorProviderImpl; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.FaceScanningOverlayProviderImpl; @@ -157,6 +157,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private DisplayInfo mDisplayInfo; private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener; + @Mock + private CutoutDecorProviderFactory mCutoutFactory; + private List<DecorProvider> mMockCutoutList; @Before public void setup() { @@ -206,6 +209,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { DisplayCutout.BOUNDS_POSITION_RIGHT, R.layout.privacy_dot_bottom_right)); + // Default no cutout + mMockCutoutList = new ArrayList<>(); + doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders(); + doReturn(mMockCutoutList).when(mCutoutFactory).getProviders(); + mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl( BOUNDS_POSITION_TOP, mAuthController, @@ -239,6 +247,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { super.updateOverlayWindowVisibilityIfViewExists(view); mExecutor.runAllReady(); } + + @Override + protected CutoutDecorProviderFactory getCutoutFactory() { + return ScreenDecorationsTest.this.mCutoutFactory; + } }); mScreenDecorations.mDisplayInfo = mDisplayInfo; doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); @@ -429,11 +442,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_NoCutout_NoPrivacyDot_NoFaceScanning() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // No views added. @@ -448,11 +459,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_NoCutout_PrivacyDot_NoFaceScanning() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -484,11 +493,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_NoCutout_NoPrivacyDot_NoFaceScanning() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -516,11 +523,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_NoCutout_PrivacyDot_NoFaceScanning() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -555,10 +560,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); + + // no cutout (default) mScreenDecorations.start(); // Size of corner view should same as rounded_corner_radius{_top|_bottom} @@ -574,11 +578,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() @@ -611,13 +613,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() @@ -647,13 +646,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutShortEdge_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for top cutout. @@ -671,13 +667,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutShortEdge_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for top cutout. @@ -706,13 +699,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutLongEdge_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for left cutout. @@ -734,13 +724,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutLongEdge_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for left cutout. @@ -762,13 +749,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -791,13 +775,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -823,13 +804,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutLongEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for rounded corner and left cutout. @@ -842,13 +820,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutLongEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for rounded corner, left cutout, and privacy. @@ -863,13 +838,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortAndLongEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top and left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -883,13 +856,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortAndLongEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top and left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -905,21 +876,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // Set to short edge cutout(top). - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE); // Switch to long edge cutout(left). - final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.onConfigurationChanged(new Configuration()); verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE); @@ -929,13 +895,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // Set to short edge cutout(top). - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); @@ -943,9 +906,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { verify(mDotViewController, times(1)).setShowingListener(null); // Switch to long edge cutout(left). - final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.onConfigurationChanged(new Configuration()); verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE); @@ -973,20 +934,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testDelayedCutout_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + // No cutout (default) mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, false, false, false, null); - when(mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout)) - .thenReturn(true); + // top cutout + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); + mScreenDecorations.onConfigurationChanged(new Configuration()); // Only top windows should be added. @@ -997,13 +954,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testDelayedCutout_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // Both top and bottom windows should be added with INVISIBLE because of only privacy dot, @@ -1015,9 +968,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { verify(mDotViewController, times(1)).setShowingListener( mScreenDecorations.mPrivacyDotShowingListener); - when(mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout)) - .thenReturn(true); + // top cutout + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); + mScreenDecorations.onConfigurationChanged(new Configuration()); // Both top and bottom windows should be added with VISIBLE because of privacy dot and @@ -1043,8 +996,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning*/); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); mDisplayInfo.rotation = Surface.ROTATION_0; mScreenDecorations.start(); @@ -1058,8 +1010,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning*/); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); mDisplayInfo.rotation = Surface.ROTATION_270; mScreenDecorations.onConfigurationChanged(null); @@ -1072,8 +1023,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnlyRoundedCornerRadiusTop() { setupResources(0 /* radius */, 10 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1094,8 +1044,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnlyRoundedCornerRadiusBottom() { setupResources(0 /* radius */, 0 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1166,13 +1115,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testSupportHwcLayer_SwitchFrom_NotSupport() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // should only inflate mOverlays when the hwc doesn't support screen decoration @@ -1195,16 +1141,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNotSupportHwcLayer_SwitchFrom_Support() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // should only inflate hwc layer when the hwc supports screen decoration @@ -1234,16 +1177,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer @@ -1277,11 +1217,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testAutoShowHideOverlayWindowWhenNoRoundedAndNoCutout() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer @@ -1315,16 +1253,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHwcLayer_noPrivacyDot_noFaceScanning() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1337,16 +1272,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHwcLayer_PrivacyDot_FaceScanning() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1364,16 +1296,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnDisplayChanged_hwcLayer() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1390,18 +1319,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnDisplayChanged_nonHwcLayer() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); - final ScreenDecorations.DisplayCutoutView cutoutView = - mScreenDecorations.mCutoutViews[BOUNDS_POSITION_TOP]; + final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) + mScreenDecorations.getOverlayView(R.id.display_cutout); + assertNotNull(cutoutView); spyOn(cutoutView); doReturn(mDisplay).when(cutoutView).getDisplay(); @@ -1414,8 +1341,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHasSameProvidersWithNullOverlays() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1433,8 +1359,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHasSameProvidersWithPrivacyDots() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1471,7 +1396,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private void setupResources(int radius, int radiusTop, int radiusBottom, @Nullable Drawable roundedTopDrawable, @Nullable Drawable roundedBottomDrawable, - int roundedPadding, boolean fillCutout, boolean privacyDot, boolean faceScanning) { + int roundedPadding, boolean privacyDot, boolean faceScanning) { mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.array.config_displayUniqueIdArray, new String[]{}); @@ -1511,8 +1436,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } mContext.getOrCreateTestableResources().addOverride( R.dimen.rounded_corner_content_padding, roundedPadding); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout); mPrivacyDecorProviders = new ArrayList<>(); if (privacyDot) { @@ -1531,19 +1454,4 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(faceScanning); } - - private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) { - final int rotation = mContext.getDisplay().getRotation(); - final Insets insets = RotationUtils.rotateInsets(safeInsets, rotation); - final Rect[] sorted = new Rect[BOUNDS_POSITION_LENGTH]; - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - final int rotatedPos = ScreenDecorations.getBoundPositionFromRotation(i, rotation); - if (cutoutBounds[i] != null) { - RotationUtils.rotateBounds(cutoutBounds[i], new Rect(0, 0, 100, 200), rotation); - } - sorted[rotatedPos] = cutoutBounds[i]; - } - return new DisplayCutout(insets, sorted[BOUNDS_POSITION_LEFT], sorted[BOUNDS_POSITION_TOP], - sorted[BOUNDS_POSITION_RIGHT], sorted[BOUNDS_POSITION_BOTTOM]); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index e0d1f7a19130..ce49ced103a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -20,6 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -75,6 +77,8 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import android.view.DisplayInfo; +import android.view.Surface; import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -157,13 +161,15 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private InteractionJankMonitor mInteractionJankMonitor; @Captor - ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor; + private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor; + @Captor + private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor; @Captor - ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor; + private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor; @Captor - ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor; + private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; @Captor - ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; + private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefullnessObserverCaptor; private TestableContext mContextSpy; private Execution mExecution; @@ -242,7 +248,9 @@ public class AuthControllerTest extends SysuiTestCase { mFaceAuthenticatorsRegisteredCaptor.capture()); when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); + verify(mWakefulnessLifecycle).addObserver(mWakefullnessObserverCaptor.capture()); mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps); mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps); @@ -720,13 +728,8 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testSubscribesToOrientationChangesWhenShowingDialog() { - showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */); - + public void testSubscribesToOrientationChangesOnStart() { verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong()); - - mAuthController.hideAuthenticationDialog(REQUEST_ID); - verify(mDisplayManager).unregisterDisplayListener(any()); } @Test @@ -759,19 +762,148 @@ public class AuthControllerTest extends SysuiTestCase { } @Test - public void testForwardsDozeEvent() throws RemoteException { + public void testForwardsDozeEvents() throws RemoteException { + when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); mAuthController.setBiometicContextListener(mContextListener); - mStatusBarStateListenerCaptor.getValue().onDozingChanged(false); mStatusBarStateListenerCaptor.getValue().onDozingChanged(true); + mStatusBarStateListenerCaptor.getValue().onDozingChanged(false); InOrder order = inOrder(mContextListener); - // invoked twice since the initial state is false - order.verify(mContextListener, times(2)).onDozeChanged(eq(false)); - order.verify(mContextListener).onDozeChanged(eq(true)); + order.verify(mContextListener, times(2)).onDozeChanged(eq(true), eq(true)); + order.verify(mContextListener).onDozeChanged(eq(false), eq(true)); + order.verifyNoMoreInteractions(); } - // Helpers + @Test + public void testForwardsWakeEvents() throws RemoteException { + when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); + mAuthController.setBiometicContextListener(mContextListener); + + mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep(); + mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep(); + mWakefullnessObserverCaptor.getValue().onStartedWakingUp(); + mWakefullnessObserverCaptor.getValue().onFinishedWakingUp(); + mWakefullnessObserverCaptor.getValue().onPostFinishedWakingUp(); + + InOrder order = inOrder(mContextListener); + order.verify(mContextListener).onDozeChanged(eq(false), eq(true)); + order.verify(mContextListener).onDozeChanged(eq(false), eq(false)); + order.verify(mContextListener).onDozeChanged(eq(false), eq(true)); + order.verifyNoMoreInteractions(); + } + + @Test + public void testGetFingerprintSensorLocationChanges_differentRotations() { + // GIVEN fp default location and mocked device dimensions + // Rotation 0, where "o" is the location of the FP sensor, if x or y = 0, it's the edge of + // the screen which is why a 1x1 width and height is represented by a 2x2 grid below: + // [* o] + // [* *] + Point fpDefaultLocation = new Point(1, 0); + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1; + displayInfo.logicalHeight = 1; + + // WHEN the rotation is 0, THEN no rotation applied + displayInfo.rotation = Surface.ROTATION_0; + assertEquals( + fpDefaultLocation, + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // WHEN the rotation is 270, THEN rotation is applied + // [* *] + // [* o] + displayInfo.rotation = Surface.ROTATION_270; + assertEquals( + new Point(1, 1), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // WHEN the rotation is 180, THEN rotation is applied + // [* *] + // [o *] + displayInfo.rotation = Surface.ROTATION_180; + assertEquals( + new Point(0, 1), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // WHEN the rotation is 90, THEN rotation is applied + // [o *] + // [* *] + displayInfo.rotation = Surface.ROTATION_90; + assertEquals( + new Point(0, 0), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + } + + @Test + public void testGetFingerprintSensorLocationChanges_rotateRectangle() { + // GIVEN fp default location and mocked device dimensions + // Rotation 0, where "o" is the location of the FP sensor, if x or y = 0, it's the edge of + // the screen. + // [* * o *] + // [* * * *] + Point fpDefaultLocation = new Point(2, 0); + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 3; + displayInfo.logicalHeight = 1; + + // WHEN the rotation is 0, THEN no rotation applied + displayInfo.rotation = Surface.ROTATION_0; + assertEquals( + fpDefaultLocation, + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // WHEN the rotation is 180, THEN rotation is applied + // [* * * *] + // [* o * *] + displayInfo.rotation = Surface.ROTATION_180; + assertEquals( + new Point(1, 1), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // Rotation 270 & 90 have swapped logical width and heights + displayInfo.logicalWidth = 1; + displayInfo.logicalHeight = 3; + + // WHEN the rotation is 270, THEN rotation is applied + // [* *] + // [* *] + // [* o] + // [* *] + displayInfo.rotation = Surface.ROTATION_270; + assertEquals( + new Point(1, 2), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + + // WHEN the rotation is 90, THEN rotation is applied + // [* *] + // [o *] + // [* *] + // [* *] + displayInfo.rotation = Surface.ROTATION_90; + assertEquals( + new Point(0, 1), + mAuthController.rotateToCurrentOrientation( + new Point(fpDefaultLocation), displayInfo) + ); + } private void showDialog(int[] sensorIds, boolean credentialAllowed) { mAuthController.showAuthenticationDialog(createTestPromptInfo(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index d6afd6d192ec..44ef922d2c39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.biometrics -import android.graphics.PointF +import android.graphics.Point import android.hardware.biometrics.BiometricSourceType import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -31,11 +31,12 @@ import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils +import javax.inject.Provider import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -45,15 +46,14 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness -import javax.inject.Provider @SmallTest @RunWith(AndroidTestingRunner::class) @@ -116,7 +116,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_KeyguardVisible_Ripple() { // GIVEN fp exists, keyguard is visible, user doesn't need strong auth - val fpsLocation = PointF(5f, 5f) + val fpsLocation = Point(5, 5) `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) @@ -139,7 +139,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_Dreaming_Ripple() { // GIVEN fp exists, keyguard is visible, user doesn't need strong auth - val fpsLocation = PointF(5f, 5f) + val fpsLocation = Point(5, 5) `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(false) @@ -162,7 +162,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_KeyguardNotVisible_NotDreaming_NoRipple() { // GIVEN fp exists & user doesn't need strong auth - val fpsLocation = PointF(5f, 5f) + val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false) @@ -184,7 +184,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_StrongAuthRequired_NoRipple() { // GIVEN fp exists & keyguard is visible - val fpsLocation = PointF(5f, 5f) + val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) @@ -205,8 +205,8 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFaceTriggerBypassEnabled_Ripple() { // GIVEN face auth sensor exists, keyguard is visible & strong auth isn't required - val faceLocation = PointF(5f, 5f) - `when`(authController.faceAuthSensorLocation).thenReturn(faceLocation) + val faceLocation = Point(5, 5) + `when`(authController.faceSensorLocation).thenReturn(faceLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) @@ -229,8 +229,8 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFaceTriggerNonBypass_NoRipple() { // GIVEN face auth sensor exists - val faceLocation = PointF(5f, 5f) - `when`(authController.faceAuthSensorLocation).thenReturn(faceLocation) + val faceLocation = Point(5, 5) + `when`(authController.faceSensorLocation).thenReturn(faceLocation) controller.onViewAttached() // WHEN bypass isn't enabled & face authenticated @@ -248,7 +248,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testNullFaceSensorLocationDoesNothing() { - `when`(authController.faceAuthSensorLocation).thenReturn(null) + `when`(authController.faceSensorLocation).thenReturn(null) controller.onViewAttached() val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) @@ -293,7 +293,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test @RunWithLooper(setAsMainLooper = true) fun testAnimatorRunWhenWakeAndUnlock_fingerprint() { - val fpsLocation = PointF(5f, 5f) + val fpsLocation = Point(5, 5) `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) @@ -311,8 +311,8 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test @RunWithLooper(setAsMainLooper = true) fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() { - val faceLocation = PointF(5f, 5f) - `when`(authController.faceAuthSensorLocation).thenReturn(faceLocation) + val faceLocation = Point(5, 5) + `when`(authController.faceSensorLocation).thenReturn(faceLocation) controller.onViewAttached() `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt index 3ac28c8d0d37..2af055783c22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt @@ -107,11 +107,11 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { reset(rippleView) captor.value.onThemeChanged() - verify(rippleView).setColor(ArgumentMatchers.anyInt()) + verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()) reset(rippleView) captor.value.onUiModeChanged() - verify(rippleView).setColor(ArgumentMatchers.anyInt()) + verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt new file mode 100644 index 000000000000..1040ec453a7c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.graphics.Insets +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableResources +import android.util.RotationUtils +import android.util.Size +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH +import android.view.DisplayInfo +import android.view.Surface +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class CutoutDecorProviderFactoryTest : SysuiTestCase() { + + @Mock private lateinit var display: Display + private var testableRes: TestableResources? = null + private lateinit var factory: CutoutDecorProviderFactory + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableRes = mContext.orCreateTestableResources + factory = CutoutDecorProviderFactory(testableRes!!.resources, display) + } + + private fun setupFillCutout(fillCutout: Boolean) { + testableRes!!.addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout + ) + } + + private fun setupDisplayInfo( + displayCutout: DisplayCutout? = null, + @Surface.Rotation rotation: Int = Surface.ROTATION_0, + displayId: Int = -1 + ) { + doAnswer { + it.getArgument<DisplayInfo>(0).let { info -> + info.displayCutout = displayCutout + info.rotation = rotation + info.displayId = displayId + } + true + }.`when`(display).getDisplayInfo(any<DisplayInfo>()) + } + + private fun getCutout( + safeInsets: Insets, + cutoutBounds: Array<Rect?>, + @Surface.Rotation rotation: Int = Surface.ROTATION_0, + cutoutParentSizeForRotate: Size = Size(100, 200) + ): DisplayCutout { + val insets = RotationUtils.rotateInsets(safeInsets, rotation) + val sorted = arrayOfNulls<Rect>(BOUNDS_POSITION_LENGTH) + for (pos in 0 until BOUNDS_POSITION_LENGTH) { + val rotatedPos = (pos - rotation + BOUNDS_POSITION_LENGTH) % BOUNDS_POSITION_LENGTH + if (cutoutBounds[pos] != null) { + RotationUtils.rotateBounds( + cutoutBounds[pos], + cutoutParentSizeForRotate.width, + cutoutParentSizeForRotate.height, + rotation + ) + } + sorted[rotatedPos] = cutoutBounds[pos] + } + return DisplayCutout( + insets, + sorted[DisplayCutout.BOUNDS_POSITION_LEFT], + sorted[DisplayCutout.BOUNDS_POSITION_TOP], + sorted[DisplayCutout.BOUNDS_POSITION_RIGHT], + sorted[DisplayCutout.BOUNDS_POSITION_BOTTOM] + ) + } + + @Test + fun testGetNothingIfNoCutout() { + setupFillCutout(false) + + Assert.assertFalse(factory.hasProviders) + Assert.assertEquals(0, factory.providers.size) + } + + @Test + fun testGetTopCutoutProvider() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 1, 0, 0), + cutoutBounds = arrayOf(null, Rect(9, 0, 10, 1), null, null) + ) + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, providers[0].alignedBounds[0]) + } + + @Test + fun testGetBottomCutoutProviderOnLandscape() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 0, 0, 1), + cutoutBounds = arrayOf(null, null, null, Rect(45, 199, 55, 200)), + rotation = Surface.ROTATION_90 + ), + Surface.ROTATION_90 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_BOTTOM, providers[0].alignedBounds[0]) + } + + @Test + fun testGetLeftCutoutProviderOnSeascape() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(1, 0, 0, 0), + cutoutBounds = arrayOf(Rect(0, 20, 1, 40), null, null, null), + rotation = Surface.ROTATION_270 + ), + Surface.ROTATION_270 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_LEFT, providers[0].alignedBounds[0]) + } + + @Test + fun testGetTopRightCutoutProviderOnReverse() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 1, 1, 0), + cutoutBounds = arrayOf( + null, + Rect(9, 0, 10, 1), + Rect(99, 40, 100, 60), + null + ), + rotation = Surface.ROTATION_180 + ), + Surface.ROTATION_180 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(2, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(1, providers[1].numOfAlignedBound) + providers.sortedBy { it.alignedBounds[0] }.let { + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, it[0].alignedBounds[0]) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_RIGHT, it[1].alignedBounds[0]) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java new file mode 100644 index 000000000000..bc944404efe6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.media.dream.MediaDreamComplication; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DreamMediaEntryComplicationTest extends SysuiTestCase { + @Mock + private View mView; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private MediaDreamComplication mMediaComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures clicking media entry chip adds/removes media complication. + */ + @Test + public void testClick() { + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + clickListenerCaptor.getValue().onClick(mView); + verify(mView).setSelected(true); + verify(mDreamOverlayStateController).addComplication(mMediaComplication); + clickListenerCaptor.getValue().onClick(mView); + verify(mView).setSelected(false); + verify(mDreamOverlayStateController).removeComplication(mMediaComplication); + } + + /** + * Ensures media complication is removed when the view is detached. + */ + @Test + public void testOnViewDetached() { + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication); + + viewController.onViewDetached(); + verify(mView).setSelected(false); + verify(mDreamOverlayStateController).removeComplication(mMediaComplication); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java index 7d54758acee7..fa8f88a08368 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java @@ -43,7 +43,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Arrays; +import java.util.Collections; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -61,9 +61,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { private SmartSpaceComplication mComplication; @Mock - private ComplicationViewModel mComplicationViewModel; - - @Mock private View mBcSmartspaceView; @Before @@ -125,12 +122,12 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { // Test final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); - listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.singletonList(target)); verify(mDreamOverlayStateController).addComplication(eq(mComplication)); } @Test - public void testOverlayActive_targetsEmpty_removesComplication() { + public void testOverlayActive_targetsEmpty_addsComplication() { final SmartSpaceComplication.Registrant registrant = getRegistrant(); registrant.start(); @@ -145,13 +142,9 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); verify(mSmartspaceController).addListener(listenerCaptor.capture()); - final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); - listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); - verify(mDreamOverlayStateController).addComplication(eq(mComplication)); - // Test - listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList()); - verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.emptyList()); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); } @Test @@ -170,8 +163,7 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); verify(mSmartspaceController).addListener(listenerCaptor.capture()); - final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); - listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Collections.emptyList()); verify(mDreamOverlayStateController).addComplication(eq(mComplication)); // Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index 5ec6bdf3c00b..27a5190367a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -32,7 +32,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; -import android.graphics.PointF; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; @@ -127,7 +127,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; - @Captor private ArgumentCaptor<PointF> mPointCaptor; + @Captor private ArgumentCaptor<Point> mPointCaptor; @Before public void setUp() throws Exception { @@ -178,7 +178,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdateFingerprintLocationOnInit() { // GIVEN fp sensor location is available pre-attached - Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location + Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location // WHEN lock icon view controller is initialized and attached mLockIconViewController.init(); @@ -193,7 +193,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdatePaddingBasedOnResolutionScale() { // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5 - Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location + Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location when(mAuthController.getScaleFactor()).thenReturn(5f); // WHEN lock icon view controller is initialized and attached @@ -218,7 +218,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { // GIVEN fp sensor location is available post-attached captureAuthControllerCallback(); - Pair<Float, PointF> udfps = setupUdfps(); + Pair<Float, Point> udfps = setupUdfps(); // WHEN all authenticators are registered mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); @@ -241,7 +241,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { // GIVEN fp sensor location is available post-attached captureAuthControllerCallback(); - Pair<Float, PointF> udfps = setupUdfps(); + Pair<Float, Point> udfps = setupUdfps(); // WHEN udfps location changes mAuthControllerCallback.onUdfpsLocationChanged(); @@ -421,9 +421,9 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).setTranslationX(0); } - private Pair<Float, PointF> setupUdfps() { + private Pair<Float, Point> setupUdfps() { when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); - final PointF udfpsLocation = new PointF(50, 75); + final Point udfpsLocation = new Point(50, 75); final float radius = 33f; when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation); when(mAuthController.getUdfpsRadius()).thenReturn(radius); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 61736923fb82..9be201e99b2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -224,6 +224,14 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { } @Test + public void refresh_inDragging_directSetRefreshingToFalse() { + when(mMediaOutputBaseAdapter.isDragging()).thenReturn(true); + mMediaOutputBaseDialogImpl.refresh(); + + assertThat(mMediaOutputController.isRefreshing()).isFalse(); + } + + @Test public void refresh_notInDragging_verifyUpdateAdapter() { when(mMediaOutputBaseAdapter.getCurrentActivePosition()).thenReturn(-1); when(mMediaOutputBaseAdapter.isDragging()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index c101b9ffd495..2e864dc2cda8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -18,6 +18,7 @@ package com.android.systemui.media.dream; import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -30,6 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; @@ -51,7 +53,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { DreamOverlayStateController mDreamOverlayStateController; @Mock - MediaDreamComplication mComplication; + DreamMediaEntryComplication mMediaEntryComplication; @Mock FeatureFlags mFeatureFlags; @@ -72,7 +74,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testComplicationAddition() { final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, - mDreamOverlayStateController, mComplication, mFeatureFlags); + mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -85,14 +87,16 @@ public class MediaDreamSentinelTest extends SysuiTestCase { when(mMediaDataManager.hasActiveMedia()).thenReturn(true); listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); - verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + verify(mDreamOverlayStateController).addComplication(eq(mMediaEntryComplication)); + verify(mDreamOverlayStateController, never()).addComplication( + not(eq(mMediaEntryComplication))); listener.onMediaDataRemoved(mKey); verify(mDreamOverlayStateController, never()).removeComplication(any()); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); listener.onMediaDataRemoved(mKey); - verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); + verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication)); } @Test @@ -100,7 +104,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, - mDreamOverlayStateController, mComplication, mFeatureFlags); + mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index 9b0142d6a8fe..5db3b9caa007 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.data.source.UserRecord import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -39,8 +40,8 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -140,13 +141,14 @@ class UserDetailViewAdapterTest : SysuiTestCase() { } private fun createUserRecord(current: Boolean, guest: Boolean) = - UserSwitcherController.UserRecord( - mUserInfo, - mPicture, - guest, - current, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + UserRecord( + mUserInfo, + mPicture, + guest, + current, + false /* isAddUser */, + false /* isRestricted */, + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 83e56daf1fbc..6ce9cff9a730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -40,6 +40,7 @@ import com.android.internal.util.ScreenshotHelper.ScreenshotRequest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR +import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback @@ -47,12 +48,15 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock +import java.util.function.Consumer import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.isNull +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever @@ -88,7 +92,16 @@ class TakeScreenshotServiceTest : SysuiTestCase() { .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) + // Stub request processor as a synchronous no-op for tests with the flag enabled + doAnswer { + val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest + val consumer: Consumer<ScreenshotRequest> = it.getArgument(1) + consumer.accept(request) + }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any()) + + // Flipped in selected test cases flags.set(SCREENSHOT_REQUEST_PROCESSOR, false) + flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false) service.attach( mContext, @@ -105,10 +118,10 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.onBind(null /* unused: Intent */) service.onUnbind(null /* unused: Intent */) - verify(controller).removeWindow() + verify(controller, times(1)).removeWindow() service.onDestroy() - verify(controller).onDestroy() + verify(controller, times(1)).onDestroy() } @Test @@ -120,7 +133,32 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */ }, callback) - verify(controller).takeScreenshotFullscreen( + verify(controller, times(1)).takeScreenshotFullscreen( + eq(topComponent), + /* onSavedListener = */ any(), + /* requestCallback = */ any()) + + assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) + val logEvent = eventLogger.get(0) + + assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", + logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) + assertEquals("Expected supplied package name", + topComponent.packageName, eventLogger.get(0).packageName) + } + + @Test + fun takeScreenshot_requestProcessorEnabled() { + flags.set(SCREENSHOT_REQUEST_PROCESSOR, true) + + val request = ScreenshotRequest( + TAKE_SCREENSHOT_FULLSCREEN, + SCREENSHOT_KEY_CHORD, + topComponent) + + service.handleRequest(request, { /* onSaved */ }, callback) + + verify(controller, times(1)).takeScreenshotFullscreen( eq(topComponent), /* onSavedListener = */ any(), /* requestCallback = */ any()) @@ -143,7 +181,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */ }, callback) - verify(controller).takeScreenshotPartial( + verify(controller, times(1)).takeScreenshotPartial( /* topComponent = */ isNull(), /* onSavedListener = */ any(), /* requestCallback = */ any()) @@ -167,7 +205,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */ }, callback) - verify(controller).handleImageAsScreenshot( + verify(controller, times(1)).handleImageAsScreenshot( argThat { b -> b.equalsHardwareBitmap(bitmap) }, eq(bounds), eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent), @@ -193,8 +231,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */ }, callback) - verify(notificationsController).notifyScreenshotError(anyInt()) - verify(callback).reportError() + verify(notificationsController, times(1)).notifyScreenshotError(anyInt()) + verify(callback, times(1)).reportError() verifyZeroInteractions(controller) } @@ -217,7 +255,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { service.handleRequest(request, { /* onSaved */ }, callback) // error shown: Toast.makeText(...).show(), untestable - verify(callback).reportError() + verify(callback, times(1)).reportError() verifyZeroInteractions(controller) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 98389c2c7a6f..e2ce939cb66c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -458,7 +458,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { NotificationWakeUpCoordinator coordinator = new NotificationWakeUpCoordinator( - mDumpManager, mock(HeadsUpManagerPhone.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, mInteractionJankMonitor), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 351dd0c49519..05692b3e6749 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -570,6 +570,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void onBiometricHelp_coEx_faceFailure() { + createController(); + + // GIVEN unlocking with fingerprint is possible + when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) + .thenReturn(true); + + String message = "A message"; + mController.setVisible(true); + + // WHEN there's a face not recognized message + mController.getKeyguardCallback().onBiometricHelp( + KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, + message, + BiometricSourceType.FACE); + + // THEN show sequential messages such as: 'face not recognized' and + // 'try fingerprint instead' + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + mContext.getString(R.string.keyguard_face_failed)); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_suggest_fingerprint)); + } + + @Test public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() { createController(); String message = mContext.getString(R.string.keyguard_unlock); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java deleted file mode 100644 index 19dd027daedd..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification; - -import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; - -import android.app.ActivityManager; -import android.app.Notification; -import android.os.UserHandle; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class NotificationListControllerTest extends SysuiTestCase { - private NotificationListController mController; - - @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationListContainer mListContainer; - @Mock private DeviceProvisionedController mDeviceProvisionedController; - - @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; - @Captor private ArgumentCaptor<DeviceProvisionedListener> mProvisionedCaptor; - - private NotificationEntryListener mEntryListener; - private DeviceProvisionedListener mProvisionedListener; - - private int mNextNotifId = 0; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mDependency.injectMockDependency(NotificationLockscreenUserManager.class); - - mController = new NotificationListController( - mEntryManager, - mListContainer, - mDeviceProvisionedController); - mController.bind(); - - // Capture callbacks passed to mocks - verify(mEntryManager).addNotificationEntryListener(mEntryListenerCaptor.capture()); - mEntryListener = mEntryListenerCaptor.getValue(); - verify(mDeviceProvisionedController).addCallback(mProvisionedCaptor.capture()); - mProvisionedListener = mProvisionedCaptor.getValue(); - } - - @Test - public void testCleanUpViewStateOnEntryRemoved() { - final NotificationEntry entry = buildEntry(); - mEntryListener.onEntryRemoved( - entry, - NotificationVisibility.obtain(entry.getKey(), 0, 0, true), - false, - UNDEFINED_DISMISS_REASON); - verify(mListContainer).cleanUpViewStateForEntry(entry); - } - - @Test - public void testCallUpdateNotificationsOnDeviceProvisionedChange() { - mProvisionedListener.onDeviceProvisionedChanged(); - verify(mEntryManager).updateNotifications(anyString()); - } - - private NotificationEntry buildEntry() { - mNextNotifId++; - - Notification.Builder n = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); - - return new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_NAME) - .setOpPkg(TEST_PACKAGE_NAME) - .setId(mNextNotifId) - .setUid(TEST_UID) - .setNotification(n.build()) - .setUser(new UserHandle(ActivityManager.getCurrentUser())) - .build(); - } - - private static final String TEST_PACKAGE_NAME = "test"; - private static final int TEST_UID = 0; -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java deleted file mode 100644 index b20f95cfad3a..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification.row; - -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; -import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; - -import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.NotificationChannel; -import android.content.Context; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.View; - -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link NotificationBlockingHelperManager}. - */ -@SmallTest -@FlakyTest -@org.junit.runner.RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class NotificationBlockingHelperManagerTest extends SysuiTestCase { - private NotificationBlockingHelperManager mBlockingHelperManager; - - private NotificationTestHelper mHelper; - - @Mock private NotificationGutsManager mGutsManager; - @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationMenuRow mMenuRow; - @Mock private NotificationMenuRowPlugin.MenuItem mMenuItem; - @Mock private GroupMembershipManager mGroupMembershipManager; - - @Before - public void setUp() { - allowTestableLooperAsMainThread(); - MockitoAnnotations.initMocks(this); - when(mGutsManager.openGuts( - any(View.class), - anyInt(), - anyInt(), - any(NotificationMenuRowPlugin.MenuItem.class))) - .thenReturn(true); - when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem); - - mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); - - mBlockingHelperManager = new NotificationBlockingHelperManager( - mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class), - mGroupMembershipManager); - // By default, have the shade visible/expanded. - mBlockingHelperManager.setNotificationShadeExpanded(1f); - } - - @Test - public void testDismissCurrentBlockingHelper_nullBlockingHelperRow() { - // By default, this shouldn't dismiss (no pointers/vars set up!) - assertFalse(mBlockingHelperManager.dismissCurrentBlockingHelper()); - assertTrue(mBlockingHelperManager.isBlockingHelperRowNull()); - } - - @Test - public void testDismissCurrentBlockingHelper_withDetachedBlockingHelperRow() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - row.setBlockingHelperShowing(true); - when(row.isAttachedToWindow()).thenReturn(false); - mBlockingHelperManager.setBlockingHelperRowForTest(row); - - assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper()); - assertTrue(mBlockingHelperManager.isBlockingHelperRowNull()); - - verify(mEntryManager, times(0)).updateNotifications(anyString()); - } - - @Test - public void testDismissCurrentBlockingHelper_withAttachedBlockingHelperRow() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - row.setBlockingHelperShowing(true); - when(row.isAttachedToWindow()).thenReturn(true); - mBlockingHelperManager.setBlockingHelperRowForTest(row); - - assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper()); - assertTrue(mBlockingHelperManager.isBlockingHelperRowNull()); - - verify(mEntryManager).updateNotifications(anyString()); - } - - @Test - public void testPerhapsShowBlockingHelper_shown() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - - assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - - verify(mGutsManager).openGuts(row, 0, 0, mMenuItem); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownForMultiChannelGroup() throws Exception { - ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10); - int i = 0; - for (ExpandableNotificationRow childRow : groupRow.getAttachedChildren()) { - modifyRanking(childRow.getEntry()) - .setChannel( - new NotificationChannel( - Integer.toString(i++), "", IMPORTANCE_DEFAULT)) - .build(); - } - - modifyRanking(groupRow.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow)); - - verify(mGutsManager, never()).openGuts(groupRow, 0, 0, mMenuItem); - } - - @Test - public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception { - ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10); - modifyRanking(groupRow.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - - assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow)); - - verify(mGutsManager).openGuts(groupRow, 0, 0, mMenuItem); - } - - @Test - public void testPerhapsShowBlockingHelper_shownForOnlyChildNotification() - throws Exception { - ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(1); - // Explicitly get the children container & call getViewAtPosition on it instead of the row - // as other factors such as view expansion may cause us to get the parent row back instead - // of the child row. - ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0); - modifyRanking(childRow.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - assertFalse(childRow.getIsNonblockable()); - - when(mGroupMembershipManager.isOnlyChildInGroup(childRow.getEntry())).thenReturn(true); - assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow)); - - verify(mGutsManager).openGuts(childRow, 0, 0, mMenuItem); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownDueToNeutralUserSentiment() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEUTRAL) - .build(); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownDueToPositiveUserSentiment() - throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_POSITIVE) - .build(); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownDueToShadeVisibility() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - // Hide the shade - mBlockingHelperManager.setNotificationShadeExpanded(0f); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownDueToNonblockability() throws Exception { - ExpandableNotificationRow row = createBlockableRowSpy(); - when(row.getIsNonblockable()).thenReturn(true); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - } - - @Test - public void testPerhapsShowBlockingHelper_notShownAsNotificationIsInMultipleChildGroup() - throws Exception { - ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(2); - // Explicitly get the children container & call getViewAtPosition on it instead of the row - // as other factors such as view expansion may cause us to get the parent row back instead - // of the child row. - ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0); - modifyRanking(childRow.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - - assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow)); - } - - @Test - public void testBlockingHelperShowAndDismiss() throws Exception{ - ExpandableNotificationRow row = createBlockableRowSpy(); - modifyRanking(row.getEntry()) - .setUserSentiment(USER_SENTIMENT_NEGATIVE) - .build(); - when(row.isAttachedToWindow()).thenReturn(true); - - // Show check - assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow)); - - verify(mGutsManager).openGuts(row, 0, 0, mMenuItem); - - // Dismiss check - assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper()); - assertTrue(mBlockingHelperManager.isBlockingHelperRowNull()); - - verify(mEntryManager).updateNotifications(anyString()); - } - - @Test - public void testNonBlockable_package() { - mBlockingHelperManager.setNonBlockablePkgs(new String[] {"banana", "strawberry:pie"}); - - assertFalse(mBlockingHelperManager.isNonblockable("orange", "pie")); - - assertTrue(mBlockingHelperManager.isNonblockable("banana", "pie")); - } - - @Test - public void testNonBlockable_channel() { - mBlockingHelperManager.setNonBlockablePkgs(new String[] {"banana", "strawberry:pie"}); - - assertFalse(mBlockingHelperManager.isNonblockable("strawberry", "shortcake")); - - assertTrue(mBlockingHelperManager.isNonblockable("strawberry", "pie")); - } - - private ExpandableNotificationRow createBlockableRowSpy() throws Exception { - ExpandableNotificationRow row = spy(mHelper.createRow()); - when(row.getIsNonblockable()).thenReturn(false); - return row; - } - - private ExpandableNotificationRow createBlockableGroupRowSpy(int numChildren) throws Exception { - ExpandableNotificationRow row = spy(mHelper.createGroup(numChildren)); - when(row.getIsNonblockable()).thenReturn(false); - return row; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 8fd6842911de..35d2363b1c2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -3,19 +3,21 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.EmptyShadeView +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView -import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController -import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue -import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock @@ -26,17 +28,20 @@ class StackScrollAlgorithmTest : SysuiTestCase() { private val hostView = FrameLayout(context) private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView) - private val expandableViewState = ExpandableViewState() private val notificationRow = mock(ExpandableNotificationRow::class.java) private val dumpManager = mock(DumpManager::class.java) private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java) + private val notificationShelf = mock(NotificationShelf::class.java) + private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { + layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) + } private val ambientState = AmbientState( - context, - dumpManager, - SectionProvider { _, _ -> false }, - BypassController { false }, - mStatusBarKeyguardViewManager + context, + dumpManager, + /* sectionProvider */ { _, _ -> false }, + /* bypassController */ { false }, + mStatusBarKeyguardViewManager ) private val testableResources = mContext.orCreateTestableResources @@ -49,7 +54,9 @@ class StackScrollAlgorithmTest : SysuiTestCase() { @Before fun setUp() { - whenever(notificationRow.viewState).thenReturn(expandableViewState) + whenever(notificationShelf.viewState).thenReturn(ExpandableViewState()) + whenever(notificationRow.viewState).thenReturn(ExpandableViewState()) + hostView.addView(notificationRow) } @@ -60,7 +67,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, 0) - assertThat(expandableViewState.yTranslation).isEqualTo(stackScrollAlgorithm.mHeadsUpInset) + assertThat(notificationRow.viewState.yTranslation) + .isEqualTo(stackScrollAlgorithm.mHeadsUpInset) } @Test @@ -75,15 +83,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, 0) // top margin presence should decrease heads up translation up to minHeadsUpTranslation - assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation) + assertThat(notificationRow.viewState.yTranslation).isEqualTo(minHeadsUpTranslation) } @Test fun resetViewStates_emptyShadeView_isCenteredVertically() { stackScrollAlgorithm.initView(context) - val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { - layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) - } hostView.removeAllViews() hostView.addView(emptyShadeView) ambientState.layoutMaxHeight = 1280 @@ -98,6 +103,121 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + fun resetViewStates_hunGoingToShade_viewBecomesOpaque() { + whenever(notificationRow.isAboveShelf).thenReturn(true) + ambientState.isShadeExpanded = true + ambientState.trackedHeadsUpRow = notificationRow + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(notificationRow.viewState.alpha).isEqualTo(1f) + } + + @Test + fun resetViewStates_isExpansionChanging_viewBecomesTransparent() { + whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false) + ambientState.isExpansionChanging = true + ambientState.expansionFraction = 0.25f + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val expected = getContentAlpha(0.25f) + assertThat(notificationRow.viewState.alpha).isEqualTo(expected) + } + + @Test + fun resetViewStates_isExpansionChangingWhileBouncerInTransit_viewBecomesTransparent() { + whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true) + ambientState.isExpansionChanging = true + ambientState.expansionFraction = 0.25f + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val expected = aboutToShowBouncerProgress(0.25f) + assertThat(notificationRow.viewState.alpha).isEqualTo(expected) + } + + @Test + fun resetViewStates_isOnKeyguard_viewBecomesTransparent() { + ambientState.setStatusBarState(StatusBarState.KEYGUARD) + ambientState.hideAmount = 0.25f + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(notificationRow.viewState.alpha).isEqualTo(1f - ambientState.hideAmount) + } + + @Test + fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesTransparent() { + ambientState.setStatusBarState(StatusBarState.KEYGUARD) + ambientState.fractionToShade = 0.25f + stackScrollAlgorithm.initView(context) + hostView.removeAllViews() + hostView.addView(emptyShadeView) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + val expected = getContentAlpha(ambientState.fractionToShade) + assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected) + } + + @Test + fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() { + ambientState.setStatusBarState(StatusBarState.SHADE) + ambientState.fractionToShade = 0.25f + stackScrollAlgorithm.initView(context) + hostView.removeAllViews() + hostView.addView(emptyShadeView) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f) + } + + @Test + fun resetViewStates_hiddenShelf_viewAlphaDoesNotChange() { + val expected = notificationShelf.viewState.alpha + notificationShelf.viewState.hidden = true + ambientState.shelf = notificationShelf + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(notificationShelf.viewState.alpha).isEqualTo(expected) + } + + @Test + fun resetViewStates_shelfTopLessThanViewTop_hidesView() { + notificationRow.viewState.yTranslation = 10f + notificationShelf.viewState.yTranslation = 0.9f + notificationShelf.viewState.hidden = false + ambientState.shelf = notificationShelf + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(notificationRow.viewState.alpha).isEqualTo(0f) + } + + @Test + fun resetViewStates_shelfTopGreaterOrEqualThanViewTop_viewAlphaDoesNotChange() { + val expected = notificationRow.viewState.alpha + notificationRow.viewState.yTranslation = 10f + notificationShelf.viewState.yTranslation = 10f + notificationShelf.viewState.hidden = false + ambientState.shelf = notificationShelf + stackScrollAlgorithm.initView(context) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(notificationRow.viewState.alpha).isEqualTo(expected) + } + + @Test fun getGapForLocation_onLockscreen_returnsSmallGap() { val gap = stackScrollAlgorithm.getGapForLocation( /* fractionToShade= */ 0f, /* onKeyguard= */ true) @@ -267,7 +387,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertEquals(10f, expandableViewState.yTranslation) } - @Test fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() { val expandableViewState = ExpandableViewState() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index a6b7e5103c78..ca98143044c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -58,7 +58,6 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { @Before public void setup() { injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); - mDependency.injectMockDependency(DarkIconDispatcher.class); } @Test @@ -75,7 +74,8 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { layout, mock(FeatureFlags.class), mock(StatusBarPipelineFlags.class), - () -> mock(WifiViewModel.class)); + () -> mock(WifiViewModel.class), + mock(DarkIconDispatcher.class)); testCallOnAdd_forManager(manager); } @@ -116,8 +116,10 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { LinearLayout group, FeatureFlags featureFlags, StatusBarPipelineFlags statusBarPipelineFlags, - Provider<WifiViewModel> wifiViewModelProvider) { - super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider); + Provider<WifiViewModel> wifiViewModelProvider, + DarkIconDispatcher darkIconDispatcher) { + super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider, + darkIconDispatcher); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index 982927148a2e..d070ba0e47be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -473,6 +473,40 @@ class WifiRepositoryImplTest : SysuiTestCase() { job.cancel() } + /** Regression test for b/244173280. */ + @Test + fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = runBlocking(IMMEDIATE) { + var latest1: WifiNetworkModel? = null + val job1 = underTest + .wifiNetwork + .onEach { latest1 = it } + .launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + assertThat(latest1 is WifiNetworkModel.Active).isTrue() + val latest1Active = latest1 as WifiNetworkModel.Active + assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID) + assertThat(latest1Active.ssid).isEqualTo(SSID) + + // WHEN we add a second subscriber after having already emitted a value + var latest2: WifiNetworkModel? = null + val job2 = underTest + .wifiNetwork + .onEach { latest2 = it } + .launchIn(this) + + // THEN the second subscribe receives the already-emitted value + assertThat(latest2 is WifiNetworkModel.Active).isTrue() + val latest2Active = latest2 as WifiNetworkModel.Active + assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID) + assertThat(latest2Active.ssid).isEqualTo(SSID) + + job1.cancel() + job2.cancel() + } + @Test fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) { underTest = WifiRepositoryImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index 0dd6cbb7995a..c3805ad36533 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -28,6 +28,7 @@ import com.android.internal.util.UserIcons import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.qs.tiles.UserDetailItemView +import com.android.systemui.user.data.source.UserRecord import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -38,8 +39,8 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -186,13 +187,14 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { } private fun createUserRecord(isCurrentUser: Boolean, isGuestUser: Boolean) = - UserSwitcherController.UserRecord( - userInfo, - picture, - isGuestUser, - isCurrentUser, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + UserRecord( + userInfo, + picture, + isGuestUser, + isCurrentUser, + false /* isAddUser */, + false /* isRestricted */, + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 359a780d2a94..8dcd4bb3b738 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.settings.UserTracker import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.telephony.TelephonyListenerManager +import com.android.systemui.user.data.source.UserRecord import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -235,15 +236,16 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testSwitchUser_parentDialogDismissed() { - val otherUserRecord = UserSwitcherController.UserRecord( - secondaryUser, - picture, - false /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + val otherUserRecord = UserRecord( + secondaryUser, + picture, + false /* guest */, + false /* current */, + false /* isAddUser */, + false /* isRestricted */, + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -255,7 +257,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testAddGuest_okButtonPressed() { - val emptyGuestUserRecord = UserSwitcherController.UserRecord( + val emptyGuestUserRecord = + UserRecord( null, null, true /* guest */, @@ -263,7 +266,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -282,7 +286,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testAddGuest_parentDialogDismissed() { - val emptyGuestUserRecord = UserSwitcherController.UserRecord( + val emptyGuestUserRecord = + UserRecord( null, null, true /* guest */, @@ -290,7 +295,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -305,7 +311,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testRemoveGuest_removeButtonPressed_isLogged() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -313,7 +320,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -331,7 +339,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testRemoveGuest_removeButtonPressed_dialogDismissed() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -339,7 +348,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -353,7 +363,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testRemoveGuest_dialogShowerUsed() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -361,7 +372,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -376,7 +388,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testRemoveGuest_cancelButtonPressed_isNotLogged() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -384,7 +397,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -398,7 +412,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testWipeGuest_startOverButtonPressed_isLogged() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -406,7 +421,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -433,7 +449,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun testWipeGuest_continueButtonPressed_isLogged() { - val currentGuestUserRecord = UserSwitcherController.UserRecord( + val currentGuestUserRecord = + UserRecord( guestInfo, picture, true /* guest */, @@ -441,7 +458,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -470,11 +488,13 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() { fun addUser(id: Int, name: String, isCurrent: Boolean) { - userSwitcherController.users.add(UserSwitcherController.UserRecord( + userSwitcherController.users.add( + UserRecord( UserInfo(id, name, 0), null, false, isCurrent, false, false, false, false - )) + ) + ) } val bgUserName = "background_user" val fgUserName = "foreground_user" @@ -593,7 +613,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Test fun onUserItemClicked_guest_runsOnBgThread() { val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java) - val guestUserRecord = UserSwitcherController.UserRecord( + val guestUserRecord = UserRecord( null, picture, true /* guest */, @@ -601,7 +621,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */) + false /* isAddSupervisedUser */ + ) userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower) assertTrue(bgExecutor.numPending() > 0) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6a6d2bb44d48..2b858ad045d8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -683,8 +683,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", FLAGS_PACKAGE_BROADCAST_RECEIVER, - "intent=" + intent + ";packages=" + packages + ";uid=" + uid - + ";doit=" + doit); + "intent=" + intent + ";packages=" + Arrays.toString(packages) + + ";uid=" + uid + ";doit=" + doit); } synchronized (mLock) { final int userId = getChangingUserId(); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java index f731c440d5f3..d20fa8ecebdf 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java @@ -447,7 +447,7 @@ class MultiFingerSwipe extends GestureMatcher { StringBuilder builder = new StringBuilder(super.toString()); if (getState() != STATE_GESTURE_CANCELED) { builder.append(", mBase: ") - .append(mBase.toString()) + .append(Arrays.toString(mBase)) .append(", mMinPixelsBetweenSamplesX:") .append(mMinPixelsBetweenSamplesX) .append(", mMinPixelsBetweenSamplesY:") diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 907daa34826f..aec5f5ef9ff9 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -2830,7 +2830,7 @@ public class UserBackupManagerService { + " includekeyvalue=" + doKeyValue + " pkgs=" - + pkgList)); + + Arrays.toString(pkgList))); } Slog.i(TAG, addUserIdToLogMessage(mUserId, "Beginning adb backup...")); diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java index 5dacdb40713b..d0300ff19178 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java +++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java @@ -25,9 +25,9 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.Nullable; import android.app.backup.BackupManager.OperationType; import android.app.backup.BackupTransport; +import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; -import android.app.compat.CompatChanges; import android.compat.annotation.Overridable; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -46,6 +46,7 @@ import com.android.server.backup.transport.TransportConnection; import com.google.android.collect.Sets; +import java.util.Arrays; import java.util.Set; /** @@ -360,8 +361,8 @@ public class BackupEligibilityRules { } if (DEBUG) { - Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" - + signingInfo.getApkContentsSigners()); + Slog.v(TAG, "signaturesMatch(): stored=" + Arrays.toString(storedSigs) + + " device=" + Arrays.toString(signingInfo.getApkContentsSigners())); } final int nStored = storedSigs.length; diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 3d8dc148b7e2..bd1ecb23611e 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1305,4 +1305,7 @@ public abstract class PackageManagerInternal { * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}. */ public abstract @SignatureResult int checkUidSignaturesForAllUsers(int uid1, int uid2); + + public abstract void setPackageStoppedState(@NonNull String packageName, boolean stopped, + @UserIdInt int userId); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 01af23da0f99..a580f985dd13 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4236,9 +4236,8 @@ public final class ActiveServices { // Service is now being launched, its package can't be stopped. try { - AppGlobals.getPackageManager().setPackageStoppedState( + mAm.mPackageManagerInt.setPackageStoppedState( r.packageName, false, r.userId); - } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.packageName + ": " + e); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index be0335e93d40..2cf24fa9df1f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -659,7 +659,7 @@ public class ActivityManagerService extends IActivityManager.Stub final BroadcastQueue mFgOffloadBroadcastQueue; // Convenient for easy iteration over the queues. Foreground is first // so that dispatch of foreground broadcasts gets precedence. - final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[4]; + final BroadcastQueue[] mBroadcastQueues; @GuardedBy("this") BroadcastStats mLastBroadcastStats; @@ -670,25 +670,33 @@ public class ActivityManagerService extends IActivityManager.Stub TraceErrorLogger mTraceErrorLogger; BroadcastQueue broadcastQueueForIntent(Intent intent) { - if (isOnFgOffloadQueue(intent.getFlags())) { + return broadcastQueueForFlags(intent.getFlags(), intent); + } + + BroadcastQueue broadcastQueueForFlags(int flags) { + return broadcastQueueForFlags(flags, null); + } + + BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) { + if (isOnFgOffloadQueue(flags)) { if (DEBUG_BROADCAST_BACKGROUND) { Slog.i(TAG_BROADCAST, - "Broadcast intent " + intent + " on foreground offload queue"); + "Broadcast intent " + cookie + " on foreground offload queue"); } return mFgOffloadBroadcastQueue; } - if (isOnBgOffloadQueue(intent.getFlags())) { + if (isOnBgOffloadQueue(flags)) { if (DEBUG_BROADCAST_BACKGROUND) { Slog.i(TAG_BROADCAST, - "Broadcast intent " + intent + " on background offload queue"); + "Broadcast intent " + cookie + " on background offload queue"); } return mBgOffloadBroadcastQueue; } - final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; + final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0; if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST, - "Broadcast intent " + intent + " on " + "Broadcast intent " + cookie + " on " + (isFg ? "foreground" : "background") + " queue"); return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue; } @@ -2323,7 +2331,7 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread); mIntentFirewall = null; - mProcessStats = null; + mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); mCpHelper = new ContentProviderHelper(this, false); mServices = null; mSystemThread = null; @@ -2343,6 +2351,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPendingStartActivityUids = new PendingStartActivityUids(); mUseFifoUiScheduling = false; mEnableOffloadQueue = false; + mBroadcastQueues = new BroadcastQueue[0]; mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue = mFgOffloadBroadcastQueue = null; mComponentAliasResolver = new ComponentAliasResolver(this); @@ -2401,6 +2410,7 @@ public class ActivityManagerService extends IActivityManager.Stub mEnableOffloadQueue = SystemProperties.getBoolean( "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true); + mBroadcastQueues = new BroadcastQueue[4]; mFgBroadcastQueue = new BroadcastQueueImpl(this, mHandler, "foreground", foreConstants, false); mBgBroadcastQueue = new BroadcastQueueImpl(this, mHandler, @@ -6727,9 +6737,8 @@ public class ActivityManagerService extends IActivityManager.Stub // TODO: how set package stopped state should work for sdk sandboxes? if (!isSdkSandbox) { try { - AppGlobals.getPackageManager().setPackageStoppedState( + mPackageManagerInt.setPackageStoppedState( info.packageName, false, UserHandle.getUserId(app.uid)); - } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + info.packageName + ": " + e); @@ -8739,12 +8748,40 @@ public class ActivityManagerService extends IActivityManager.Stub if (process.info.isInstantApp()) { sb.append("Instant-App: true\n"); } + if (isSdkSandboxUid(process.uid)) { + final int appUid = Process.getAppUidForSdkSandboxUid(process.uid); + try { + String[] clientPackages = pm.getPackagesForUid(appUid); + // In shared UID case, don't add the package information + if (clientPackages.length == 1) { + appendSdkSandboxClientPackageHeader(sb, clientPackages[0], callingUserId); + } + } catch (RemoteException e) { + Slog.e(TAG, "Error getting packages for client app uid: " + appUid, e); + } sb.append("SdkSandbox: true\n"); } } } + private void appendSdkSandboxClientPackageHeader(StringBuilder sb, String pkg, int userId) { + final IPackageManager pm = AppGlobals.getPackageManager(); + sb.append("SdkSandbox-Client-Package: ").append(pkg); + try { + final PackageInfo pi = pm.getPackageInfo(pkg, 0, userId); + if (pi != null) { + sb.append(" v").append(pi.getLongVersionCode()); + if (pi.versionName != null) { + sb.append(" (").append(pi.versionName).append(")"); + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Error getting package info for SDK sandbox client: " + pkg, e); + } + sb.append("\n"); + } + private static String processClass(ProcessRecord process) { if (process == null || process.getPid() == MY_PID) { return "system_server"; @@ -12845,9 +12882,8 @@ public class ActivityManagerService extends IActivityManager.Stub // !!! TODO: currently no check here that we're already bound // Backup agent is now in use, its package can't be stopped. try { - AppGlobals.getPackageManager().setPackageStoppedState( + mPackageManagerInt.setPackageStoppedState( app.packageName, false, UserHandle.getUserId(app.uid)); - } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + app.packageName + ": " + e); @@ -13328,20 +13364,18 @@ public class ActivityManagerService extends IActivityManager.Stub final long origId = Binder.clearCallingIdentity(); try { boolean doTrim = false; - synchronized(this) { ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder()); if (rl != null) { final BroadcastRecord r = rl.curBroadcast; - if (r != null && r == r.queue.getMatchingOrderedReceiver(r)) { + if (r != null) { final boolean doNext = r.queue.finishReceiverLocked( - r, r.resultCode, r.resultData, r.resultExtras, + receiver.asBinder(), r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false); if (doNext) { doTrim = true; } } - if (rl.app != null) { rl.app.mReceivers.removeReceiver(rl); } @@ -14518,22 +14552,10 @@ public class ActivityManagerService extends IActivityManager.Stub boolean doNext = false; BroadcastRecord r; BroadcastQueue queue; - synchronized(this) { - if (isOnFgOffloadQueue(flags)) { - queue = mFgOffloadBroadcastQueue; - } else if (isOnBgOffloadQueue(flags)) { - queue = mBgOffloadBroadcastQueue; - } else { - queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0 - ? mFgBroadcastQueue : mBgBroadcastQueue; - } - - r = queue.getMatchingOrderedReceiver(who); - if (r != null) { - doNext = r.queue.finishReceiverLocked(r, resultCode, + queue = broadcastQueueForFlags(flags); + doNext = queue.finishReceiverLocked(who, resultCode, resultData, resultExtras, resultAbort, true); - } // updateOomAdjLocked() will be done here trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); } @@ -17881,6 +17903,13 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Reset the dropbox rate limiter + */ + void resetDropboxRateLimiter() { + mDropboxRateLimiter.reset(); + } + + /** * Kill processes for the user with id userId and that depend on the package named packageName */ @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 36908ce8c7b3..a42b2a48f886 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -363,6 +363,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runGetBgRestrictionLevel(pw); case "observe-foreground-process": return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface); + case "reset-dropbox-rate-limiter": + return runResetDropboxRateLimiter(); default: return handleDefaultCommands(cmd); } @@ -3577,6 +3579,11 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runResetDropboxRateLimiter() throws RemoteException { + mInternal.resetDropboxRateLimiter(); + return 0; + } + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 9be22c0e41c0..752c6b683e05 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Intent; @@ -24,8 +26,11 @@ import android.os.Handler; import android.os.IBinder; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; + import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Objects; import java.util.Set; /** @@ -34,22 +39,23 @@ import java.util.Set; public abstract class BroadcastQueue { public static final String TAG = "BroadcastQueue"; - final ActivityManagerService mService; - final Handler mHandler; - final BroadcastConstants mConstants; - final BroadcastSkipPolicy mSkipPolicy; - final String mQueueName; - - BroadcastQueue(ActivityManagerService service, Handler handler, - String name, BroadcastConstants constants) { - mService = service; - mHandler = handler; - mQueueName = name; - mConstants = constants; - mSkipPolicy = new BroadcastSkipPolicy(service); + final @NonNull ActivityManagerService mService; + final @NonNull Handler mHandler; + final @NonNull BroadcastConstants mConstants; + final @NonNull BroadcastSkipPolicy mSkipPolicy; + final @NonNull String mQueueName; + + BroadcastQueue(@NonNull ActivityManagerService service, @NonNull Handler handler, + @NonNull String name, @NonNull BroadcastConstants constants, + @NonNull BroadcastSkipPolicy skipPolicy) { + mService = Objects.requireNonNull(service); + mHandler = Objects.requireNonNull(handler); + mQueueName = Objects.requireNonNull(name); + mConstants = Objects.requireNonNull(constants); + mSkipPolicy = Objects.requireNonNull(skipPolicy); } - void start(ContentResolver resolver) { + void start(@NonNull ContentResolver resolver) { mConstants.startObserving(mHandler, resolver); } @@ -60,9 +66,11 @@ public abstract class BroadcastQueue { public abstract boolean isDelayBehindServices(); - public abstract BroadcastRecord getPendingBroadcastLocked(); + @GuardedBy("mService") + public abstract @Nullable BroadcastRecord getPendingBroadcastLocked(); - public abstract BroadcastRecord getActiveBroadcastLocked(); + @GuardedBy("mService") + public abstract @Nullable BroadcastRecord getActiveBroadcastLocked(); /** * Enqueue the given broadcast to be eventually dispatched. @@ -73,9 +81,8 @@ public abstract class BroadcastQueue { * When {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} is set, this method * internally handles replacement of any matching broadcasts. */ - public abstract void enqueueBroadcastLocked(BroadcastRecord r); - - public abstract BroadcastRecord getMatchingOrderedReceiver(IBinder receiver); + @GuardedBy("mService") + public abstract void enqueueBroadcastLocked(@NonNull BroadcastRecord r); /** * Signal delivered back from a {@link BroadcastReceiver} to indicate that @@ -83,44 +90,55 @@ public abstract class BroadcastQueue { * <p> * If this signal isn't delivered back in a timely fashion, we assume the * receiver has somehow wedged and we trigger an ANR. + * + * @param receiver the value to match against + * {@link BroadcastRecord#receiver} to identify the caller. */ - public abstract boolean finishReceiverLocked(BroadcastRecord r, int resultCode, - String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices); + @GuardedBy("mService") + public abstract boolean finishReceiverLocked(@NonNull IBinder receiver, int resultCode, + @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort, + boolean waitForServices); + @GuardedBy("mService") public abstract void backgroundServicesFinishedLocked(int userId); /** * Signal from OS internals that the given process has just been actively * attached, and is ready to begin receiving broadcasts. */ - public abstract boolean onApplicationAttachedLocked(ProcessRecord app); + @GuardedBy("mService") + public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app); /** * Signal from OS internals that the given process has timed out during * an attempted start and attachment. */ - public abstract boolean onApplicationTimeoutLocked(ProcessRecord app); + @GuardedBy("mService") + public abstract boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app); /** * Signal from OS internals that the given process, which had already been * previously attached, has now encountered a problem such as crashing or * not responding. */ - public abstract boolean onApplicationProblemLocked(ProcessRecord app); + @GuardedBy("mService") + public abstract boolean onApplicationProblemLocked(@NonNull ProcessRecord app); /** * Signal from OS internals that the given process has been killed, and is * no longer actively running. */ - public abstract boolean onApplicationCleanupLocked(ProcessRecord app); + @GuardedBy("mService") + public abstract boolean onApplicationCleanupLocked(@NonNull ProcessRecord app); /** * Signal from OS internals that the given package (or some subset of that * package) has been disabled or uninstalled, and that any pending * broadcasts should be cleaned up. */ - public abstract boolean cleanupDisabledPackageReceiversLocked( - String packageName, Set<String> filterByClasses, int userId, boolean doit); + @GuardedBy("mService") + public abstract boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName, + @Nullable Set<String> filterByClasses, int userId, boolean doit); /** * Quickly determine if this queue has broadcasts that are still waiting to @@ -133,7 +151,7 @@ public abstract class BroadcastQueue { /** * Brief summary of internal state, useful for debugging purposes. */ - public abstract String describeState(); + public abstract @NonNull String describeState(); /** * Flush any broadcasts still waiting to be delivered, causing them to be @@ -143,8 +161,10 @@ public abstract class BroadcastQueue { */ public abstract void flush(); - public abstract void dumpDebug(ProtoOutputStream proto, long fieldId); + public abstract void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId); - public abstract boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll, String dumpPackage, boolean needSep); + @GuardedBy("mService") + public abstract boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, + @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage, + boolean needSep); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 2b3b21182a6c..d9146121fc27 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -42,7 +42,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; -import android.app.AppGlobals; import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.RemoteServiceException.CannotDeliverBroadcastException; @@ -213,7 +212,14 @@ public class BroadcastQueueImpl extends BroadcastQueue { BroadcastQueueImpl(ActivityManagerService service, Handler handler, String name, BroadcastConstants constants, boolean allowDelayBehindServices) { - super(service, handler, name, constants); + this(service, handler, name, constants, new BroadcastSkipPolicy(service), + allowDelayBehindServices); + } + + BroadcastQueueImpl(ActivityManagerService service, Handler handler, + String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy, + boolean allowDelayBehindServices) { + super(service, handler, name, constants, skipPolicy); mHandler = new BroadcastHandler(handler.getLooper()); mDelayBehindServices = allowDelayBehindServices; mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService); @@ -520,10 +526,17 @@ public class BroadcastQueueImpl extends BroadcastQueue { public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { BroadcastRecord br = mDispatcher.getActiveBroadcastLocked(); - if (br != null && br.receiver == receiver) { - return br; + if (br == null) { + Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName + + "] no active broadcast"); + return null; } - return null; + if (br.receiver != receiver) { + Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName + + "] active broadcast " + br.receiver + " doesn't match " + receiver); + return null; + } + return br; } // > 0 only, no worry about "eventual" recycling @@ -551,6 +564,17 @@ public class BroadcastQueueImpl extends BroadcastQueue { }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT)); } + public boolean finishReceiverLocked(IBinder receiver, int resultCode, + String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) { + final BroadcastRecord r = getMatchingOrderedReceiver(receiver); + if (r != null) { + return finishReceiverLocked(r, resultCode, + resultData, resultExtras, resultAbort, waitForServices); + } else { + return false; + } + } + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) { final int state = r.state; @@ -1354,9 +1378,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { // Broadcast is being executed, its package can't be stopped. try { - AppGlobals.getPackageManager().setPackageStoppedState( + mService.mPackageManagerInt.setPackageStoppedState( r.curComponent.getPackageName(), false, r.userId); - } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + r.curComponent.getPackageName() + ": " + e); diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 9abd01ac3ee9..78629b04cff2 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -464,10 +464,9 @@ public class ContentProviderHelper { try { checkTime(startTime, "getContentProviderImpl: before set stopped state"); - AppGlobals.getPackageManager().setPackageStoppedState( + mService.mPackageManagerInt.setPackageStoppedState( cpr.appInfo.packageName, false, userId); checkTime(startTime, "getContentProviderImpl: after set stopped state"); - } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " + cpr.appInfo.packageName + ": " + e); diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index baf062d28457..6087f76687bf 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -19,11 +19,13 @@ package com.android.server.am; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; /** Rate limiter for adding errors into dropbox. */ public class DropboxRateLimiter { + private static final String TAG = "DropboxRateLimiter"; // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has // elapsed, after which the current count for this breakdown will be reset. @@ -105,6 +107,15 @@ public class DropboxRateLimiter { mLastMapCleanUp = now; } + /** Resets the rate limiter memory. */ + void reset() { + synchronized (mErrorClusterRecords) { + mErrorClusterRecords.clear(); + } + mLastMapCleanUp = 0L; + Slog.i(TAG, "Rate limiter reset."); + } + String errorKey(String eventType, String processName) { return eventType + processName; } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ccbca76d1868..48eb0de9011f 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -133,7 +133,6 @@ import com.android.server.SystemConfig; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ProcessChangeItem; import com.android.server.compat.PlatformCompat; -import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.wm.ActivityServiceConnectionsHolder; @@ -1790,14 +1789,6 @@ public final class ProcessList { if (app.info.isEmbeddedDexUsed()) { runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; - } else if (app.info.isPrivilegedApp()) { - final PackageList pkgList = app.getPkgList(); - synchronized (pkgList) { - if (DexManager.isPackageSelectedToRunOob( - pkgList.getPackageListLocked().keySet())) { - runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; - } - } } if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) { diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java index 028288f73564..dcadd5ff518c 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -29,6 +29,7 @@ import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.IAmbientContextObserver; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -53,6 +54,8 @@ import com.android.server.infra.AbstractPerUserSystemService; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; /** * Per-user manager service for {@link AmbientContextEvent}s. @@ -165,20 +168,17 @@ final class AmbientContextManagerPerUserService extends * package. A new registration from the same package will overwrite the previous registration. */ public void onRegisterObserver(AmbientContextEventRequest request, - PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { + String packageName, IAmbientContextObserver observer) { synchronized (mLock) { if (!setUpServiceIfNeeded()) { Slog.w(TAG, "Detection service is not available at this moment."); - sendStatusCallback( - clientStatusCallback, - AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } // Register package and add to existing ClientRequests cache - startDetection(request, pendingIntent.getCreatorPackage(), - createDetectionResultRemoteCallback(), clientStatusCallback); - mMaster.newClientAdded(mUserId, request, pendingIntent, clientStatusCallback); + startDetection(request, packageName, observer); + mMaster.newClientAdded(mUserId, request, packageName, observer); } } @@ -186,49 +186,46 @@ final class AmbientContextManagerPerUserService extends * Returns a RemoteCallback that handles the status from the detection service, and * sends results to the client callback. */ - private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) { + private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) { return new RemoteCallback(result -> { AmbientContextDetectionServiceStatus serviceStatus = (AmbientContextDetectionServiceStatus) result.get( AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); final long token = Binder.clearCallingIdentity(); try { - String packageName = serviceStatus.getPackageName(); - Bundle bundle = new Bundle(); - bundle.putInt( - AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, - serviceStatus.getStatusCode()); - clientStatusCallback.sendResult(bundle); int statusCode = serviceStatus.getStatusCode(); + statusConsumer.accept(statusCode); Slog.i(TAG, "Got detection status of " + statusCode - + " for " + packageName); + + " for " + serviceStatus.getPackageName()); } finally { Binder.restoreCallingIdentity(token); } }); } - @VisibleForTesting void startDetection(AmbientContextEventRequest request, String callingPackage, - RemoteCallback detectionResultCallback, RemoteCallback clientStatusCallback) { + IAmbientContextObserver observer) { Slog.d(TAG, "Requested detection of " + request.getEventTypes()); synchronized (mLock) { if (setUpServiceIfNeeded()) { ensureRemoteServiceInitiated(); - mRemoteService.startDetection(request, callingPackage, detectionResultCallback, - getServerStatusCallback(clientStatusCallback)); + mRemoteService.startDetection(request, callingPackage, + createDetectionResultRemoteCallback(), + getServerStatusCallback( + statusCode -> completeRegistration(observer, statusCode))); } else { Slog.w(TAG, "No valid component found for AmbientContextDetectionService"); - sendStatusToCallback(clientStatusCallback, + completeRegistration(observer, AmbientContextManager.STATUS_NOT_SUPPORTED); } } } /** - * Sends an intent with a status code and empty events. + * Sends the result response with the specified status to the callback. */ - void sendStatusCallback(RemoteCallback statusCallback, int statusCode) { + static void sendStatusCallback(RemoteCallback statusCallback, + @AmbientContextManager.StatusCode int statusCode) { Bundle bundle = new Bundle(); bundle.putInt( AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, @@ -236,6 +233,15 @@ final class AmbientContextManagerPerUserService extends statusCallback.sendResult(bundle); } + static void completeRegistration(IAmbientContextObserver observer, int statusCode) { + try { + observer.onRegistrationComplete(statusCode); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: " + + e.getMessage()); + } + } + /** * Unregisters the client from all previously registered events by removing from the * mExistingRequests map, and unregister events from the service if those events are not @@ -255,7 +261,7 @@ final class AmbientContextManagerPerUserService extends synchronized (mLock) { if (!setUpServiceIfNeeded()) { Slog.w(TAG, "Detection service is not available at this moment."); - sendStatusToCallback(statusCallback, + sendStatusCallback(statusCallback, AmbientContextManager.STATUS_NOT_SUPPORTED); return; } @@ -263,7 +269,8 @@ final class AmbientContextManagerPerUserService extends mRemoteService.queryServiceStatus( eventTypes, callingPackage, - getServerStatusCallback(statusCallback)); + getServerStatusCallback( + statusCode -> sendStatusCallback(statusCallback, statusCode))); } } @@ -350,18 +357,6 @@ final class AmbientContextManagerPerUserService extends return ComponentName.unflattenFromString(consentComponent); } - /** - * Sends the result response with the specified status to the callback. - */ - void sendStatusToCallback(RemoteCallback callback, - @AmbientContextManager.StatusCode int status) { - Bundle bundle = new Bundle(); - bundle.putInt( - AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, - status); - callback.sendResult(bundle); - } - @VisibleForTesting void stopDetection(String packageName) { Slog.d(TAG, "Stop detection for " + packageName); @@ -377,13 +372,13 @@ final class AmbientContextManagerPerUserService extends * Sends out the Intent to the client after the event is detected. * * @param pendingIntent Client's PendingIntent for callback - * @param result result from the detection service + * @param events detected events from the detection service */ - private void sendDetectionResultIntent(PendingIntent pendingIntent, - AmbientContextDetectionResult result) { + void sendDetectionResultIntent(PendingIntent pendingIntent, + List<AmbientContextEvent> events) { Intent intent = new Intent(); intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS, - new ArrayList(result.getEvents())); + new ArrayList(events)); // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing // the PendingIntent as a backdoor to do this. BroadcastOptions options = BroadcastOptions.makeBasic(); @@ -392,7 +387,7 @@ final class AmbientContextManagerPerUserService extends pendingIntent.send(getContext(), 0, intent, null, null, null, options.toBundle()); Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " - + result); + + events); } catch (PendingIntent.CanceledException e) { Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent); } @@ -405,16 +400,19 @@ final class AmbientContextManagerPerUserService extends (AmbientContextDetectionResult) result.get( AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); String packageName = detectionResult.getPackageName(); - PendingIntent pendingIntent = mMaster.getPendingIntent(mUserId, packageName); - if (pendingIntent == null) { + IAmbientContextObserver observer = mMaster.getClientRequestObserver( + mUserId, packageName); + if (observer == null) { return; } final long token = Binder.clearCallingIdentity(); try { - sendDetectionResultIntent(pendingIntent, detectionResult); + observer.onEvents(detectionResult.getEvents()); Slog.i(TAG, "Got detection result of " + detectionResult.getEvents() + " for " + packageName); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call IAmbientContextObserver.onEvents: " + e.getMessage()); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java index 4206262398c7..e205e84cea26 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -27,10 +27,12 @@ import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextManager; import android.app.ambientcontext.IAmbientContextManager; +import android.app.ambientcontext.IAmbientContextObserver; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.RemoteCallback; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; @@ -48,6 +50,7 @@ import com.android.server.pm.KnownPackages; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -67,31 +70,27 @@ public class AmbientContextManagerService extends static class ClientRequest { private final int mUserId; private final AmbientContextEventRequest mRequest; - private final PendingIntent mPendingIntent; - private final RemoteCallback mClientStatusCallback; + private final String mPackageName; + private final IAmbientContextObserver mObserver; ClientRequest(int userId, AmbientContextEventRequest request, - PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { + String packageName, IAmbientContextObserver observer) { this.mUserId = userId; this.mRequest = request; - this.mPendingIntent = pendingIntent; - this.mClientStatusCallback = clientStatusCallback; + this.mPackageName = packageName; + this.mObserver = observer; } String getPackageName() { - return mPendingIntent.getCreatorPackage(); + return mPackageName; } AmbientContextEventRequest getRequest() { return mRequest; } - PendingIntent getPendingIntent() { - return mPendingIntent; - } - - RemoteCallback getClientStatusCallback() { - return mClientStatusCallback; + IAmbientContextObserver getObserver() { + return mObserver; } boolean hasUserId(int userId) { @@ -139,16 +138,16 @@ public class AmbientContextManagerService extends } void newClientAdded(int userId, AmbientContextEventRequest request, - PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { - Slog.d(TAG, "New client added: " + pendingIntent.getCreatorPackage()); + String callingPackage, IAmbientContextObserver observer) { + Slog.d(TAG, "New client added: " + callingPackage); // Remove any existing ClientRequest for this user and package. mExistingClientRequests.removeAll( - findExistingRequests(userId, pendingIntent.getCreatorPackage())); + findExistingRequests(userId, callingPackage)); // Add to existing ClientRequests mExistingClientRequests.add( - new ClientRequest(userId, request, pendingIntent, clientStatusCallback)); + new ClientRequest(userId, request, callingPackage, observer)); } void clientRemoved(int userId, String packageName) { @@ -167,10 +166,10 @@ public class AmbientContextManagerService extends } @Nullable - PendingIntent getPendingIntent(int userId, String packageName) { + IAmbientContextObserver getClientRequestObserver(int userId, String packageName) { for (ClientRequest clientRequest : mExistingClientRequests) { if (clientRequest.hasUserIdAndPackageName(userId, packageName)) { - return clientRequest.getPendingIntent(); + return clientRequest.getObserver(); } } return null; @@ -236,15 +235,13 @@ public class AmbientContextManagerService extends * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. */ void startDetection(@UserIdInt int userId, AmbientContextEventRequest request, - String packageName, RemoteCallback detectionResultCallback, - RemoteCallback statusCallback) { + String packageName, IAmbientContextObserver observer) { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); synchronized (mLock) { final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); if (service != null) { - service.startDetection(request, packageName, detectionResultCallback, - statusCallback); + service.startDetection(request, packageName, observer); } else { Slog.i(TAG, "service not available for user_id: " + userId); } @@ -297,8 +294,7 @@ public class AmbientContextManagerService extends Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName()); service.startDetection(clientRequest.getRequest(), clientRequest.getPackageName(), - service.createDetectionResultRemoteCallback(), - clientRequest.getClientStatusCallback()); + clientRequest.getObserver()); } } } @@ -328,16 +324,45 @@ public class AmbientContextManagerService extends Objects.requireNonNull(request); Objects.requireNonNull(resultPendingIntent); Objects.requireNonNull(statusCallback); + // Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the + // code unified + IAmbientContextObserver observer = new IAmbientContextObserver.Stub() { + @Override + public void onEvents(List<AmbientContextEvent> events) throws RemoteException { + mService.sendDetectionResultIntent(resultPendingIntent, events); + } + + @Override + public void onRegistrationComplete(int statusCode) throws RemoteException { + AmbientContextManagerPerUserService.sendStatusCallback(statusCallback, + statusCode); + } + }; + registerObserverWithCallback(request, resultPendingIntent.getCreatorPackage(), + observer); + } + + /** + * Register an observer for Ambient Context events. + */ + @Override + public void registerObserverWithCallback(AmbientContextEventRequest request, + String packageName, + IAmbientContextObserver observer) { + Slog.i(TAG, "AmbientContextManagerService registerObserverWithCallback."); + Objects.requireNonNull(request); + Objects.requireNonNull(packageName); + Objects.requireNonNull(observer); mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); - assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage()); + assertCalledByPackageOwner(packageName); if (!mIsServiceEnabled) { Slog.w(TAG, "Service not available."); - mService.sendStatusCallback(statusCallback, + AmbientContextManagerPerUserService.completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } - mService.onRegisterObserver(request, resultPendingIntent, statusCallback); + mService.onRegisterObserver(request, packageName, observer); } @Override @@ -359,7 +384,7 @@ public class AmbientContextManagerService extends assertCalledByPackageOwner(callingPackage); if (!mIsServiceEnabled) { Slog.w(TAG, "Detection service not available."); - mService.sendStatusToCallback(statusCallback, + AmbientContextManagerPerUserService.sendStatusCallback(statusCallback, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java index ec6c2f00b3b1..a3ffcde802f3 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java @@ -22,13 +22,15 @@ import android.annotation.NonNull; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.IAmbientContextObserver; import android.content.ComponentName; import android.os.Binder; import android.os.RemoteCallback; +import android.os.RemoteException; import android.os.ShellCommand; -import android.service.ambientcontext.AmbientContextDetectionResult; import java.io.PrintWriter; +import java.util.List; /** * Shell command for {@link AmbientContextManagerService}. @@ -39,6 +41,7 @@ final class AmbientContextShellCommand extends ShellCommand { new AmbientContextEventRequest.Builder() .addEventType(AmbientContextEvent.EVENT_COUGH) .addEventType(AmbientContextEvent.EVENT_SNORE) + .addEventType(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP) .build(); @NonNull @@ -50,11 +53,11 @@ final class AmbientContextShellCommand extends ShellCommand { /** Callbacks for AmbientContextEventService results used internally for testing. */ static class TestableCallbackInternal { - private AmbientContextDetectionResult mLastResult; + private List<AmbientContextEvent> mLastEvents; private int mLastStatus; - public AmbientContextDetectionResult getLastResult() { - return mLastResult; + public List<AmbientContextEvent> getLastEvents() { + return mLastEvents; } public int getLastStatus() { @@ -62,19 +65,19 @@ final class AmbientContextShellCommand extends ShellCommand { } @NonNull - private RemoteCallback createRemoteDetectionResultCallback() { - return new RemoteCallback(result -> { - AmbientContextDetectionResult detectionResult = - (AmbientContextDetectionResult) result.get( - AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); - final long token = Binder.clearCallingIdentity(); - try { - mLastResult = detectionResult; - out.println("Detection result available: " + detectionResult); - } finally { - Binder.restoreCallingIdentity(token); + private IAmbientContextObserver createAmbientContextObserver() { + return new IAmbientContextObserver.Stub() { + @Override + public void onEvents(List<AmbientContextEvent> events) throws RemoteException { + mLastEvents = events; + out.println("Detection events available: " + events); } - }); + + @Override + public void onRegistrationComplete(int statusCode) throws RemoteException { + mLastStatus = statusCode; + } + }; } @NonNull @@ -123,8 +126,7 @@ final class AmbientContextShellCommand extends ShellCommand { final String packageName = getNextArgRequired(); mService.startDetection( userId, REQUEST, packageName, - sTestableCallbackInternal.createRemoteDetectionResultCallback(), - sTestableCallbackInternal.createRemoteStatusCallback()); + sTestableCallbackInternal.createAmbientContextObserver()); return 0; } diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index 158092f33ee3..dd0c4b86a77d 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -816,8 +816,7 @@ final class DiscreteRegistry { } } catch (Throwable t) { - Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " " - + t.getStackTrace()); + Slog.e(TAG, "Error while cleaning timeline files: ", t); } } } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index b7e817e15452..c143675538a3 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -1115,6 +1115,9 @@ public class SpatializerHelper { && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) { setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled : Spatializer.HEAD_TRACKING_MODE_DISABLED); + if (enabled && !mHeadTrackerAvailable) { + postInitSensors(); + } } } diff --git a/services/core/java/com/android/server/backup/BackupUtils.java b/services/core/java/com/android/server/backup/BackupUtils.java index 96c562100a7a..76eba1699298 100644 --- a/services/core/java/com/android/server/backup/BackupUtils.java +++ b/services/core/java/com/android/server/backup/BackupUtils.java @@ -30,6 +30,7 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class BackupUtils { private static final String TAG = "BackupUtils"; @@ -64,8 +65,9 @@ public class BackupUtils { } if (DEBUG) { - Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes - + " device=" + signingInfo.getApkContentsSigners()); + Slog.v(TAG, "signaturesMatch(): stored=" + + storedSigHashes.stream().map(Arrays::toString).collect(Collectors.toList()) + + " device=" + Arrays.toString(signingInfo.getApkContentsSigners())); } final int nStored = storedSigHashes.size(); diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java index c86a8cb2c39d..8265203ddacd 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -46,6 +46,9 @@ public interface BiometricContext { /** If the display is in AOD. */ boolean isAod(); + /** If the device is awake or is becoming awake. */ + boolean isAwake(); + /** * Subscribe to context changes. * diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 9d2fde72ea2e..3d1a6343cbf2 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -43,7 +43,7 @@ import java.util.function.Consumer; /** * A default provider for {@link BiometricContext}. */ -class BiometricContextProvider implements BiometricContext { +final class BiometricContextProvider implements BiometricContext { private static final String TAG = "BiometricContextProvider"; @@ -76,7 +76,8 @@ class BiometricContextProvider implements BiometricContext { private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>(); private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; - private boolean mIsDozing = false; + private boolean mIsAod = false; + private boolean mIsAwake = false; @VisibleForTesting BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration, @@ -85,9 +86,14 @@ class BiometricContextProvider implements BiometricContext { try { service.setBiometicContextListener(new IBiometricContextListener.Stub() { @Override - public void onDozeChanged(boolean isDozing) { - mIsDozing = isDozing; - notifyChanged(); + public void onDozeChanged(boolean isDozing, boolean isAwake) { + isDozing = isDozing && isAodEnabled(); + final boolean changed = (mIsAod != isDozing) || (mIsAwake != isAwake); + if (changed) { + mIsAod = isDozing; + mIsAwake = isAwake; + notifyChanged(); + } } private void notifyChanged() { @@ -97,6 +103,10 @@ class BiometricContextProvider implements BiometricContext { notifySubscribers(); } } + + private boolean isAodEnabled() { + return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + } }); service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() { @Override @@ -161,7 +171,12 @@ class BiometricContextProvider implements BiometricContext { @Override public boolean isAod() { - return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + return mIsAod; + } + + @Override + public boolean isAwake() { + return mIsAwake; } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index f2c5b970d52c..f7d94c9c2b9f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -315,21 +315,27 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> private ICancellationSignal doAuthenticate() throws RemoteException { final AidlSession session = getFreshDaemon(); + final OperationContext opContext = getOperationContext(); + getBiometricContext().subscribe(opContext, ctx -> { + if (session.hasContextMethods()) { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + } + + // TODO(b/243836005): this should come via ctx + final boolean isAwake = getBiometricContext().isAwake(); + if (isAwake) { + mALSProbeCallback.getProbe().enable(); + } else { + mALSProbeCallback.getProbe().disable(); + } + }); + if (session.hasContextMethods()) { - final OperationContext opContext = getOperationContext(); - final ICancellationSignal cancel = - session.getSession().authenticateWithContext(mOperationId, opContext); - getBiometricContext() - .subscribe( - opContext, - ctx -> { - try { - session.getSession().onContextChanged(ctx); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to notify context changed", e); - } - }); - return cancel; + return session.getSession().authenticateWithContext(mOperationId, opContext); } else { return session.getSession().authenticate(mOperationId); } @@ -360,7 +366,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> try { mIsPointerDown = true; mState = STATE_STARTED; - mALSProbeCallback.getProbe().enable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { @@ -389,7 +394,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> try { mIsPointerDown = false; mState = STATE_STARTED_PAUSED_ATTEMPTED; - mALSProbeCallback.getProbe().disable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 7d2cf9deb76f..e0393b54d2c5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -94,7 +94,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; - mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */); + mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */); mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { @@ -216,7 +216,6 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps public void onPointerDown(int x, int y, float minor, float major) { try { mIsPointerDown = true; - mALSProbeCallback.getProbe().enable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { @@ -240,7 +239,6 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps public void onPointerUp() { try { mIsPointerDown = false; - mALSProbeCallback.getProbe().disable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java new file mode 100644 index 000000000000..e9640cf6b4d5 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.os.IBinder; + +import java.util.Objects; + +/** + * Calls into SurfaceFlinger for Display creation and deletion. + */ +public class DisplayControl { + private static native IBinder nativeCreateDisplay(String name, boolean secure); + private static native void nativeDestroyDisplay(IBinder displayToken); + + /** + * Create a display in SurfaceFlinger. + * + * @param name The name of the display + * @param secure Whether this display is secure. + * @return The token reference for the display in SurfaceFlinger. + */ + public static IBinder createDisplay(String name, boolean secure) { + Objects.requireNonNull(name, "name must not be null"); + return nativeCreateDisplay(name, secure); + } + + /** + * Destroy a display in SurfaceFlinger. + * + * @param displayToken The display token for the display to be destroyed. + */ + public static void destroyDisplay(IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + + nativeDestroyDisplay(displayToken); + } + +} diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 330379cf58eb..b0de844389b6 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -305,7 +305,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - SurfaceControl.destroyDisplay(getDisplayTokenLocked()); + DisplayControl.destroyDisplay(getDisplayTokenLocked()); } @Override @@ -460,7 +460,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, long presentationDeadlineNanos, int state) { synchronized (getSyncRoot()) { - IBinder displayToken = SurfaceControl.createDisplay(mName, mFlags.mSecure); + IBinder displayToken = DisplayControl.createDisplay(mName, mFlags.mSecure); mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode, DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos, mFlags, state, surfaceTexture, mNumber) { diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 479629eae7ac..38728ce106af 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -82,8 +82,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { - this(syncRoot, context, handler, listener, - (String name, boolean secure) -> SurfaceControl.createDisplay(name, secure)); + this(syncRoot, context, handler, listener, DisplayControl::createDisplay); } @VisibleForTesting @@ -296,7 +295,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - SurfaceControl.destroyDisplay(getDisplayTokenLocked()); + DisplayControl.destroyDisplay(getDisplayTokenLocked()); if (mProjection != null && mMediaProjectionCallback != null) { try { mProjection.unregisterCallback(mMediaProjectionCallback); diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index d632ee3d021c..146b003650ac 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -388,7 +388,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { String name = display.getFriendlyDisplayName(); String address = display.getDeviceAddress(); - IBinder displayToken = SurfaceControl.createDisplay(name, secure); + IBinder displayToken = DisplayControl.createDisplay(name, secure); mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, refreshRate, deviceFlags, address, surface); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); @@ -621,7 +621,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - SurfaceControl.destroyDisplay(getDisplayTokenLocked()); + DisplayControl.destroyDisplay(getDisplayTokenLocked()); } public void setNameLocked(String name) { diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index 6f890cda5964..dc1f4ddb19b9 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -561,7 +561,7 @@ class GnssNetworkConnectivityHandler { mAGpsDataConnectionIpAddr = InetAddress.getByAddress(suplIpAddr); if (DEBUG) Log.d(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr); } catch (UnknownHostException e) { - Log.e(TAG, "Bad IP Address: " + suplIpAddr, e); + Log.e(TAG, "Bad IP Address: " + Arrays.toString(suplIpAddr), e); } } diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index 04cacee32283..cbbb3362a387 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -16,6 +16,8 @@ package com.android.server.logcat; +import static android.os.Process.getParentPid; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,6 +47,7 @@ import com.android.server.SystemService; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -340,13 +343,6 @@ public final class LogcatManagerService extends SystemService { * access */ private String getPackageName(LogAccessRequest request) { - if (mActivityManagerInternal != null) { - String packageName = mActivityManagerInternal.getPackageNameByPid(request.mPid); - if (packageName != null) { - return packageName; - } - } - PackageManager pm = mContext.getPackageManager(); if (pm == null) { // Decline the logd access if PackageManager is null @@ -355,15 +351,28 @@ public final class LogcatManagerService extends SystemService { } String[] packageNames = pm.getPackagesForUid(request.mUid); - if (ArrayUtils.isEmpty(packageNames)) { // Decline the logd access if the app name is unknown Slog.e(TAG, "Unknown calling package name, declining the logd access"); return null; } - String firstPackageName = packageNames[0]; + if (mActivityManagerInternal != null) { + int pid = request.mPid; + String packageName = mActivityManagerInternal.getPackageNameByPid(pid); + while ((packageName == null || !ArrayUtils.contains(packageNames, packageName)) + && pid != -1) { + pid = getParentPid(pid); + packageName = mActivityManagerInternal.getPackageNameByPid(pid); + } + if (packageName != null && ArrayUtils.contains(packageNames, packageName)) { + return packageName; + } + } + + Arrays.sort(packageNames); + String firstPackageName = packageNames[0]; if (firstPackageName == null || firstPackageName.isEmpty()) { // Decline the logd access if the package name from uid is unknown Slog.e(TAG, "Unknown calling package name, declining the logd access"); diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index a5c762a1df0b..9a190316f4eb 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -32,7 +32,6 @@ import android.os.Handler; import android.os.PowerWhitelistManager; import android.os.UserHandle; import android.text.TextUtils; -import android.util.EventLog; import android.util.Log; import android.view.KeyEvent; @@ -118,12 +117,6 @@ final class MediaButtonReceiverHolder { int componentType = getComponentType(pendingIntent); ComponentName componentName = getComponentName(pendingIntent, componentType); if (componentName != null) { - if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) { - EventLog.writeEvent(0x534e4554, "238177121", -1, ""); - throw new IllegalArgumentException("ComponentName does not belong to " - + "sessionPackageName. sessionPackageName = " + sessionPackageName - + ", ComponentName pkg = " + componentName.getPackageName()); - } return new MediaButtonReceiverHolder(userId, pendingIntent, componentName, componentType); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1ee9a873fff9..34cd6a012925 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -50,8 +50,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; -import android.text.TextUtils; -import android.util.EventLog; import android.util.Log; import android.view.KeyEvent; @@ -916,14 +914,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName) throws RemoteException { - //mPackageName has been verified in MediaSessionService.enforcePackageName(). - if (!TextUtils.equals(sessionPackageName, mPackageName)) { - EventLog.writeEvent(0x534e4554, "238177121", -1, ""); - throw new IllegalArgumentException("sessionPackageName name does not match " - + "package name provided to MediaSessionRecord. sessionPackageName = " - + sessionPackageName + ", pkg = " - + mPackageName); - } final long token = Binder.clearCallingIdentity(); try { if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) @@ -942,15 +932,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { final long token = Binder.clearCallingIdentity(); try { - //mPackageName has been verified in MediaSessionService.enforcePackageName(). - if (receiver != null && !TextUtils.equals( - mPackageName, receiver.getPackageName())) { - EventLog.writeEvent(0x534e4554, "238177121", -1, ""); - throw new IllegalArgumentException("receiver does not belong to " - + "package name provided to MediaSessionRecord. Pkg = " + mPackageName - + ", Receiver Pkg = " + receiver.getPackageName()); - } - if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) != 0) { return; diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index de83f5ae7545..d770f71435fa 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -123,6 +123,7 @@ import static android.telephony.CarrierConfigManager.KEY_DATA_RAPID_NOTIFICATION import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_NOTIFICATION_BOOL; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -3158,7 +3159,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * active merge set [A,B], we'd return a new template that primarily matches * A, but also matches B. */ - private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template, + @VisibleForTesting(visibility = PRIVATE) + static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template, @NonNull List<String[]> mergedList) { // Now there are several types of network which uses Subscriber Id to store network // information. For instance: @@ -3168,6 +3170,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (template.getSubscriberIds().isEmpty()) return template; for (final String[] merged : mergedList) { + // In some rare cases (e.g. b/243015487), merged subscriberId list might contain + // duplicated items. Deduplication for better error handling. + final ArraySet mergedSet = new ArraySet(merged); + if (mergedSet.size() != merged.length) { + Log.wtf(TAG, "Duplicated merged list detected: " + Arrays.toString(merged)); + } // TODO: Handle incompatible subscriberIds if that happens in practice. for (final String subscriberId : template.getSubscriberIds()) { if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) { @@ -3175,7 +3183,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // a template that matches all merged subscribers. return new NetworkTemplate.Builder(template.getMatchRule()) .setWifiNetworkKeys(template.getWifiNetworkKeys()) - .setSubscriberIds(Set.of(merged)) + .setSubscriberIds(mergedSet) .setMeteredness(template.getMeteredness()) .build(); } diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index 5599b0c07377..8ce7b57c55e0 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -36,7 +36,6 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.HexDump; @@ -45,8 +44,8 @@ import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -155,7 +154,7 @@ class WatchlistLoggingHandler extends Handler { try { final String[] packageNames = mPm.getPackagesForUid(uid); if (packageNames == null || packageNames.length == 0) { - Slog.e(TAG, "Couldn't find package: " + packageNames); + Slog.e(TAG, "Couldn't find package: " + Arrays.toString(packageNames)); return false; } ai = mPm.getApplicationInfo(packageNames[0], 0); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 5a40b30551b6..0fac808fb13b 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1786,8 +1786,8 @@ abstract public class ManagedServices { * from receiving events from the profile. */ public boolean isPermittedForProfile(int userId) { - if (!mUserProfiles.isManagedProfile(userId)) { - return true; + if (!mUserProfiles.canProfileUseBoundServices(userId)) { + return false; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE); @@ -1862,10 +1862,16 @@ abstract public class ManagedServices { } } - public boolean isManagedProfile(int userId) { + public boolean canProfileUseBoundServices(int userId) { synchronized (mCurrentProfiles) { UserInfo user = mCurrentProfiles.get(userId); - return user != null && user.isManagedProfile(); + if (user == null) { + return false; + } + if (user.isManagedProfile() || user.isCloneProfile()) { + return false; + } + return true; } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index aa2a25b9cdd3..9f525d537089 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -304,6 +304,7 @@ import com.android.server.notification.toast.CustomToastRecord; import com.android.server.notification.toast.TextToastRecord; import com.android.server.notification.toast.ToastRecord; import com.android.server.pm.PackageManagerService; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.statusbar.StatusBarManagerInternal; @@ -533,6 +534,7 @@ public class NotificationManagerService extends SystemService { private UriGrantsManagerInternal mUgmInternal; private volatile RoleObserver mRoleObserver; private UserManager mUm; + private UserManagerInternal mUmInternal; private IPlatformCompat mPlatformCompat; private ShortcutHelper mShortcutHelper; private PermissionHelper mPermissionHelper; @@ -819,7 +821,8 @@ public class NotificationManagerService extends SystemService { final List<UserInfo> activeUsers = mUm.getUsers(); for (UserInfo userInfo : activeUsers) { int userId = userInfo.getUserHandle().getIdentifier(); - if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) { + if (isNASMigrationDone(userId) + || userInfo.isManagedProfile() || userInfo.isCloneProfile()) { continue; } List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); @@ -950,7 +953,9 @@ public class NotificationManagerService extends SystemService { } XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY); boolean migratedManagedServices = false; - boolean ineligibleForManagedServices = forRestore && mUm.isManagedProfile(userId); + UserInfo userInfo = mUmInternal.getUserInfo(userId); + boolean ineligibleForManagedServices = forRestore && + (userInfo.isManagedProfile() || userInfo.isCloneProfile()); int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) { @@ -1824,7 +1829,7 @@ public class NotificationManagerService extends SystemService { } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); - if (!mUserProfiles.isManagedProfile(userId)) { + if (mUserProfiles.canProfileUseBoundServices(userId)) { // reload per-user settings mSettingsObserver.update(null); // Refresh managed services @@ -1838,7 +1843,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); if (userId != USER_NULL) { mUserProfiles.updateCache(context); - if (!mUserProfiles.isManagedProfile(userId)) { + if (mUserProfiles.canProfileUseBoundServices(userId)) { allowDefaultApprovedServices(userId); } } @@ -1856,7 +1861,7 @@ public class NotificationManagerService extends SystemService { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); mAssistants.onUserUnlocked(userId); - if (!mUserProfiles.isManagedProfile(userId)) { + if (mUserProfiles.canProfileUseBoundServices(userId)) { mConditionProviders.onUserUnlocked(userId); mListeners.onUserUnlocked(userId); mZenModeHelper.onUserUnlocked(userId); @@ -2218,6 +2223,7 @@ public class NotificationManagerService extends SystemService { mPackageManagerClient = packageManagerClient; mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); + mUmInternal = LocalServices.getService(UserManagerInternal.class); mUsageStatsManagerInternal = usageStatsManagerInternal; mAppOps = appOps; mAppOpsService = iAppOps; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 6cfe093df6d0..4b6543b54f2f 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -74,7 +74,6 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.ArtStatsLogUtils; import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger; -import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.DexoptUtils; import com.android.server.pm.dex.PackageDexUsage; @@ -787,10 +786,7 @@ public class PackageDexOptimizer { */ private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter, boolean isUsedByOtherApps) { - // When an app or priv app is configured to run out of box, only verify it. - if (info.isEmbeddedDexUsed() - || (info.isPrivilegedApp() - && DexManager.isPackageSelectedToRunOob(info.packageName))) { + if (info.isEmbeddedDexUsed()) { return "verify"; } @@ -827,10 +823,7 @@ public class PackageDexOptimizer { * handling the case where the package code is used by other apps. */ private String getRealCompilerFilter(AndroidPackage pkg, String targetCompilerFilter) { - // When an app or priv app is configured to run out of box, only verify it. - if (pkg.isUseEmbeddedDex() - || (pkg.isPrivileged() - && DexManager.isPackageSelectedToRunOob(pkg.getPackageName()))) { + if (pkg.isUseEmbeddedDex()) { return "verify"; } diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index e969d93465f8..330e3313883c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -727,6 +727,12 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { return snapshot().checkUidSignaturesForAllUsers(uid1, uid2); } + @Override + public void setPackageStoppedState(@NonNull String packageName, boolean stopped, + int userId) { + mService.setPackageStoppedState(snapshot(), packageName, stopped, userId); + } + @NonNull @Override @Deprecated diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 297439f33a8b..8f89fdd40b32 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -321,4 +321,15 @@ public abstract class UserManagerInternal { * {@link UserManager#isUserVisible()} in the given display. */ public abstract boolean isUserVisible(@UserIdInt int userId, int displayId); + + /** + * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the + * user is not assigned to any display. + * + * <p>The current foreground user is associated with the main display, while other users would + * only assigned to a display if they were started with + * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. If the user is a profile + * and is running, it's assigned to its parent display. + */ + public abstract int getDisplayAssignedToUser(@UserIdInt int userId); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d8b7fdab70f7..3b898f876072 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1731,6 +1731,18 @@ public class UserManagerService extends IUserManager.Stub { return false; } + // TODO(b/239982558): add unit test + private int getDisplayAssignedToUser(@UserIdInt int userId) { + if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { + return Display.DEFAULT_DISPLAY; + } + synchronized (mUsersLock) { + return mUsersOnSecondaryDisplays == null + ? Display.INVALID_DISPLAY + : mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY); + } + } + private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) { int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser()); @@ -6695,6 +6707,11 @@ public class UserManagerService extends IUserManager.Stub { public boolean isUserVisible(int userId, int displayId) { return isUserVisibleOnDisplay(userId, displayId); } + + @Override + public int getDisplayAssignedToUser(int userId) { + return UserManagerService.this.getDisplayAssignedToUser(userId); + } } // class LocalService /** diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 17109e97ddf1..8c2da45da994 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -36,7 +36,6 @@ import android.os.FileUtils; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.Log; @@ -58,8 +57,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -81,10 +78,6 @@ public class DexManager { private static final String TAG = "DexManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; - private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = - "pm.dexopt.priv-apps-oob-list"; - // System server cannot load executable code outside system partitions. // However it can load verification data - thus we pick the "verify" compiler filter. private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify"; @@ -910,45 +903,6 @@ public class DexManager { } /** - * Returns whether the given package is in the list of privilaged apps that should run out of - * box. This only makes sense if the feature is enabled. Note that when the the OOB list is - * empty, all priv apps will run in OOB mode. - */ - public static boolean isPackageSelectedToRunOob(String packageName) { - return isPackageSelectedToRunOob(Arrays.asList(packageName)); - } - - /** - * Returns whether any of the given packages are in the list of privilaged apps that should run - * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list - * is empty, all priv apps will run in OOB mode. - */ - public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) { - return isPackageSelectedToRunOobInternal( - SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false), - SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"), - packageNamesInSameProcess); - } - - @VisibleForTesting - /* package */ static boolean isPackageSelectedToRunOobInternal(boolean isEnabled, - String whitelist, Collection<String> packageNamesInSameProcess) { - if (!isEnabled) { - return false; - } - - if ("ALL".equals(whitelist)) { - return true; - } - for (String oobPkgName : whitelist.split(",")) { - if (packageNamesInSameProcess.contains(oobPkgName)) { - return true; - } - } - return false; - } - - /** * Generates log if the archive located at {@code fileName} has uncompressed dex file that can * be direclty mapped. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1a1de0341aa0..1bb476f6e4a1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -185,6 +185,7 @@ import android.view.autofill.AutofillManagerInternal; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.accessibility.util.AccessibilityUtils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.logging.MetricsLogger; @@ -224,6 +225,7 @@ import java.io.PrintWriter; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; /** * WindowManagerPolicy implementation for the Android phone UI. This @@ -437,6 +439,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; + private Supplier<GlobalActions> mGlobalActionsFactory; private GlobalActions mGlobalActions; private Handler mHandler; @@ -914,7 +917,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else { // handled by another power key policy. - if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { + if (mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { + Slog.d(TAG, "Skip power key gesture for other policy has handled it."); mSingleKeyGestureDetector.reset(); } } @@ -928,11 +932,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Abort possibly stuck animations only when power key up without long press case. mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); } - } else { - // handled by single key or another power key policy. - if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { - mSingleKeyGestureDetector.reset(); - } } finishPowerKeyPress(); @@ -1544,7 +1543,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { void showGlobalActionsInternal() { if (mGlobalActions == null) { - mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); + mGlobalActions = mGlobalActionsFactory.get(); } final boolean keyguardShowing = isKeyguardShowingAndNotOccluded(); mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); @@ -1868,11 +1867,45 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayPolicy = mDefaultDisplayRotation.getDisplayPolicy(); } + /** Point of injection for test dependencies. */ + @VisibleForTesting + static class Injector { + private final Context mContext; + private final WindowManagerFuncs mWindowManagerFuncs; + + Injector(Context context, WindowManagerFuncs funcs) { + mContext = context; + mWindowManagerFuncs = funcs; + } + + Context getContext() { + return mContext; + } + + WindowManagerFuncs getWindowManagerFuncs() { + return mWindowManagerFuncs; + } + + AccessibilityShortcutController getAccessibilityShortcutController( + Context context, Handler handler, int initialUserId) { + return new AccessibilityShortcutController(context, handler, initialUserId); + } + + Supplier<GlobalActions> getGlobalActionsFactory() { + return () -> new GlobalActions(mContext, mWindowManagerFuncs); + } + } + /** {@inheritDoc} */ @Override - public void init(Context context, WindowManagerFuncs windowManagerFuncs) { - mContext = context; - mWindowManagerFuncs = windowManagerFuncs; + public void init(Context context, WindowManagerFuncs funcs) { + init(new Injector(context, funcs)); + } + + @VisibleForTesting + void init(Injector injector) { + mContext = injector.getContext(); + mWindowManagerFuncs = injector.getWindowManagerFuncs(); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); @@ -1887,8 +1920,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHasFeatureLeanback = mPackageManager.hasSystemFeature(FEATURE_LEANBACK); mHasFeatureAuto = mPackageManager.hasSystemFeature(FEATURE_AUTOMOTIVE); mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC); - mAccessibilityShortcutController = - new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId); + mAccessibilityShortcutController = injector.getAccessibilityShortcutController( + mContext, new Handler(), mCurrentUserId); + mGlobalActionsFactory = injector.getGlobalActionsFactory(); mLogger = new MetricsLogger(); mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal @@ -1903,7 +1937,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { res.getBoolean(com.android.internal.R.bool.config_wakeOnBackKeyPress); // Init display burn-in protection - boolean burnInProtectionEnabled = context.getResources().getBoolean( + boolean burnInProtectionEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableBurnInProtection); // Allow a system property to override this. Used by developer settings. boolean burnInProtectionDevMode = @@ -1921,7 +1955,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { maxVertical = -4; maxRadius = (isRoundWindow()) ? 6 : -1; } else { - Resources resources = context.getResources(); + Resources resources = mContext.getResources(); minHorizontal = resources.getInteger( com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset); maxHorizontal = resources.getInteger( @@ -1934,21 +1968,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_burnInProtectionMaxRadius); } mBurnInProtectionHelper = new BurnInProtectionHelper( - context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius); + mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius); } mHandler = new PolicyHandler(); mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); - mModifierShortcutManager = new ModifierShortcutManager(context); - mUiMode = context.getResources().getInteger( + mModifierShortcutManager = new ModifierShortcutManager(mContext); + mUiMode = mContext.getResources().getInteger( com.android.internal.R.integer.config_defaultUiModeType); mHomeIntent = new Intent(Intent.ACTION_MAIN, null); mHomeIntent.addCategory(Intent.CATEGORY_HOME); mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - mEnableCarDockHomeCapture = context.getResources().getBoolean( + mEnableCarDockHomeCapture = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableCarDockHomeLaunch); mCarDockIntent = new Intent(Intent.ACTION_MAIN, null); mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK); @@ -1963,7 +1997,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mVrHeadsetHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mBroadcastWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PhoneWindowManager.mBroadcastWakeLock"); mPowerKeyWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, @@ -2035,9 +2069,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { readConfigurationDependentBehaviors(); - mDisplayFoldController = DisplayFoldController.create(context, DEFAULT_DISPLAY); + mDisplayFoldController = DisplayFoldController.create(mContext, DEFAULT_DISPLAY); - mAccessibilityManager = (AccessibilityManager) context.getSystemService( + mAccessibilityManager = (AccessibilityManager) mContext.getSystemService( Context.ACCESSIBILITY_SERVICE); // register for dock events @@ -2047,7 +2081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE); filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE); filter.addAction(Intent.ACTION_DOCK_EVENT); - Intent intent = context.registerReceiver(mDockReceiver, filter); + Intent intent = mContext.registerReceiver(mDockReceiver, filter); if (intent != null) { // Retrieve current sticky dock event broadcast. mDefaultDisplayPolicy.setDockMode(intent.getIntExtra(Intent.EXTRA_DOCK_STATE, @@ -2058,13 +2092,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { filter = new IntentFilter(); filter.addAction(Intent.ACTION_DREAMING_STARTED); filter.addAction(Intent.ACTION_DREAMING_STOPPED); - context.registerReceiver(mDreamReceiver, filter); + mContext.registerReceiver(mDreamReceiver, filter); // register for multiuser-relevant broadcasts filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - context.registerReceiver(mMultiuserReceiver, filter); + mContext.registerReceiver(mMultiuserReceiver, filter); - mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); + mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_safeModeEnabledVibePattern); diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java index 5964fa49f035..ebeb145526a4 100644 --- a/services/core/java/com/android/server/power/LowPowerStandbyController.java +++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java @@ -201,6 +201,7 @@ public class LowPowerStandbyController { mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE), false, mSettingsObserver, UserHandle.USER_ALL); + initSettingsLocked(); updateSettingsLocked(); if (mIsEnabled) { @@ -212,6 +213,23 @@ public class LowPowerStandbyController { } @GuardedBy("mLock") + private void initSettingsLocked() { + final ContentResolver resolver = mContext.getContentResolver(); + if (mSupportedConfig) { + final int enabledSetting = Settings.Global.getInt(resolver, + Settings.Global.LOW_POWER_STANDBY_ENABLED, /* def= */ -1); + + // If the ENABLED setting hasn't been assigned yet, set it to its default value. + // This ensures reading the setting reflects the enabled state, without having to know + // the default value for this device. + if (enabledSetting == -1) { + Settings.Global.putInt(resolver, Settings.Global.LOW_POWER_STANDBY_ENABLED, + /* value= */ mEnabledByDefaultConfig ? 1 : 0); + } + } + } + + @GuardedBy("mLock") private void updateSettingsLocked() { final ContentResolver resolver = mContext.getContentResolver(); mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver, diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 2a1748c441dc..9ee0df918d0b 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2953,7 +2953,8 @@ public final class PowerManagerService extends SystemService mHandler.removeMessages(MSG_ATTENTIVE_TIMEOUT); - if (isBeingKeptFromInattentiveSleepLocked()) { + if (getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP + || isBeingKeptFromInattentiveSleepLocked()) { return; } @@ -2985,7 +2986,7 @@ public final class PowerManagerService extends SystemService return false; } - if (getGlobalWakefulnessLocked() != WAKEFULNESS_AWAKE) { + if (getGlobalWakefulnessLocked() == WAKEFULNESS_ASLEEP) { mInattentiveSleepWarningOverlayController.dismiss(false); return true; } else if (attentiveTimeout < 0 || isBeingKeptFromInattentiveSleepLocked() diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index df902c2916ba..49ac559bf93f 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -663,6 +663,11 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0); + if (measuredEnergyDeltas != null && !measuredEnergyDeltas.isEmpty()) { + mStats.recordMeasuredEnergyDetailsLocked(elapsedRealtime, uptime, + mMeasuredEnergySnapshot.getMeasuredEnergyDetails(measuredEnergyDeltas)); + } + if ((updateFlags & UPDATE_CPU) != 0) { if (useLatestStates) { onBattery = mStats.isOnBatteryLocked(); diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 968f9161b3c1..5fdd006933ee 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -7404,6 +7404,16 @@ public class BatteryStatsImpl extends BatteryStats { return names; } + /** + * Adds measured energy delta to battery history. + */ + @GuardedBy("this") + public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs, + long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) { + mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs, + measuredEnergyDetails); + } + @GuardedBy("this") @Override public long getStartClockTime() { final long currentTimeMs = mClock.currentTimeMillis(); diff --git a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java index b55c79928e62..c8b4e3671eb3 100644 --- a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java @@ -23,19 +23,17 @@ import android.hardware.power.stats.EnergyConsumer; import android.hardware.power.stats.EnergyConsumerAttribution; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryStats.MeasuredEnergyDetails; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseLongArray; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; /** * Keeps snapshots of data from previously pulled EnergyConsumerResults. */ -@VisibleForTesting public class MeasuredEnergySnapshot { private static final String TAG = "MeasuredEnergySnapshot"; @@ -87,6 +85,8 @@ public class MeasuredEnergySnapshot { */ private final SparseArray<SparseLongArray> mAttributionSnapshots; + private MeasuredEnergyDetails mMeasuredEnergyDetails; + /** * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers * exist and what their details are. @@ -128,6 +128,28 @@ public class MeasuredEnergySnapshot { /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->chargeUC} maps. */ public @Nullable SparseLongArray[] otherUidChargesUC = null; + + boolean isEmpty() { + return bluetoothChargeUC <= 0 + && isEmpty(cpuClusterChargeUC) + && isEmpty(displayChargeUC) + && gnssChargeUC <= 0 + && mobileRadioChargeUC <= 0 + && wifiChargeUC <= 0 + && isEmpty(otherTotalChargeUC); + } + + private boolean isEmpty(long[] values) { + if (values == null) { + return true; + } + for (long value: values) { + if (value > 0) { + return false; + } + } + return true; + } } /** @@ -394,4 +416,119 @@ public class MeasuredEnergySnapshot { // since the last snapshot. Round off to the nearest whole long. return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV; } + + /** + * Converts the MeasuredEnergyDeltaData object to MeasuredEnergyDetails, which can + * be saved in battery history. + */ + MeasuredEnergyDetails getMeasuredEnergyDetails( + MeasuredEnergySnapshot.MeasuredEnergyDeltaData delta) { + if (mMeasuredEnergyDetails == null) { + mMeasuredEnergyDetails = createMeasuredEnergyDetails(); + } + + final long[] chargeUC = mMeasuredEnergyDetails.chargeUC; + for (int i = 0; i < mMeasuredEnergyDetails.consumers.length; i++) { + MeasuredEnergyDetails.EnergyConsumer energyConsumer = + mMeasuredEnergyDetails.consumers[i]; + switch (energyConsumer.type) { + case EnergyConsumerType.BLUETOOTH: + chargeUC[i] = delta.bluetoothChargeUC; + break; + case EnergyConsumerType.CPU_CLUSTER: + if (delta.cpuClusterChargeUC != null) { + chargeUC[i] = delta.cpuClusterChargeUC[energyConsumer.ordinal]; + } else { + chargeUC[i] = UNAVAILABLE; + } + break; + case EnergyConsumerType.DISPLAY: + if (delta.displayChargeUC != null) { + chargeUC[i] = delta.displayChargeUC[energyConsumer.ordinal]; + } else { + chargeUC[i] = UNAVAILABLE; + } + break; + case EnergyConsumerType.GNSS: + chargeUC[i] = delta.gnssChargeUC; + break; + case EnergyConsumerType.MOBILE_RADIO: + chargeUC[i] = delta.mobileRadioChargeUC; + break; + case EnergyConsumerType.WIFI: + chargeUC[i] = delta.wifiChargeUC; + break; + case EnergyConsumerType.OTHER: + if (delta.otherTotalChargeUC != null) { + chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal]; + } else { + chargeUC[i] = UNAVAILABLE; + } + break; + default: + chargeUC[i] = UNAVAILABLE; + break; + } + } + return mMeasuredEnergyDetails; + } + + private MeasuredEnergyDetails createMeasuredEnergyDetails() { + MeasuredEnergyDetails details = new MeasuredEnergyDetails(); + details.consumers = + new MeasuredEnergyDetails.EnergyConsumer[mEnergyConsumers.size()]; + for (int i = 0; i < mEnergyConsumers.size(); i++) { + EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i); + MeasuredEnergyDetails.EnergyConsumer consumer = + new MeasuredEnergyDetails.EnergyConsumer(); + consumer.type = energyConsumer.type; + consumer.ordinal = energyConsumer.ordinal; + switch (consumer.type) { + case EnergyConsumerType.BLUETOOTH: + consumer.name = "BLUETOOTH"; + break; + case EnergyConsumerType.CPU_CLUSTER: + consumer.name = "CPU"; + break; + case EnergyConsumerType.DISPLAY: + consumer.name = "DISPLAY"; + break; + case EnergyConsumerType.GNSS: + consumer.name = "GNSS"; + break; + case EnergyConsumerType.MOBILE_RADIO: + consumer.name = "MOBILE_RADIO"; + break; + case EnergyConsumerType.WIFI: + consumer.name = "WIFI"; + break; + case EnergyConsumerType.OTHER: + consumer.name = sanitizeCustomBucketName(energyConsumer.name); + break; + default: + consumer.name = "UNKNOWN"; + break; + } + if (consumer.type != EnergyConsumerType.OTHER) { + boolean hasOrdinal = consumer.ordinal != 0; + if (!hasOrdinal) { + // See if any other EnergyConsumer of the same type has an ordinal + for (int j = 0; j < mEnergyConsumers.size(); j++) { + EnergyConsumer aConsumer = mEnergyConsumers.valueAt(j); + if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) { + hasOrdinal = true; + break; + } + } + } + if (hasOrdinal) { + consumer.name = consumer.name + "/" + energyConsumer.ordinal; + } + } + details.consumers[i] = consumer; + } + + details.chargeUC = new long[details.consumers.length]; + return details; + } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index c04b19558770..804689a219b4 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -422,7 +422,7 @@ final class AccessibilityController { if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { mAccessibilityTracing.logTrace(TAG + ".onSomeWindowResizedOrMoved", FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, - "displayIds={" + displayIds.toString() + "}", "".getBytes(), callingUid); + "displayIds={" + Arrays.toString(displayIds) + "}", "".getBytes(), callingUid); } // Not relevant for the display magnifier. for (int i = 0; i < displayIds.length; i++) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 58e1d053d433..63dcbf85ed32 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -79,6 +79,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; +import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY; @@ -8809,9 +8810,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { - if (info.applicationInfo == null || !info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || ( - info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) - && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation()))) { + if (info.applicationInfo == null) { + return info.getMinAspectRatio(); + } + + if (!info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO)) { + return info.getMinAspectRatio(); + } + + if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) + && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation())) { + return info.getMinAspectRatio(); + } + + if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN) + && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT + && getParent().getWindowConfiguration().getWindowingMode() + == WINDOWING_MODE_FULLSCREEN) { + // We are using the parent configuration here as this is the most recent one that gets + // passed to onConfigurationChanged when a relevant change takes place return info.getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 619d693068d4..f224cc7082f6 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1866,6 +1866,16 @@ class ActivityStarter { final ActivityRecord targetTaskTop = newTask ? null : targetTask.getTopNonFinishingActivity(); if (targetTaskTop != null) { + // Removes the existing singleInstance activity in another task (if any) while + // launching a singleInstance activity on sourceRecord's task. + if (LAUNCH_SINGLE_INSTANCE == mLaunchMode && mSourceRecord != null + && targetTask == mSourceRecord.getTask()) { + final ActivityRecord activity = mRootWindowContainer.findActivity(mIntent, + mStartActivity.info, false); + if (activity != null && activity.getTask() != targetTask) { + activity.destroyIfPossible("Removes redundant singleInstance"); + } + } // Recycle the target task for this launch. startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants); if (startResult != START_SUCCESS) { @@ -2449,6 +2459,7 @@ class ActivityStarter { mInTask = null; mInTaskFragment = null; + mAddingToTaskFragment = null; mAddingToTask = false; mSourceRootTask = null; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6ee018606d71..ac9bcfb0bf01 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -61,6 +61,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -94,6 +95,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECOR import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; @@ -182,11 +184,13 @@ import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.os.WorkSource; import android.provider.Settings; import android.util.ArraySet; import android.util.DisplayMetrics; @@ -336,7 +340,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private WindowState mTmpWindow; private boolean mUpdateImeTarget; private boolean mTmpInitial; - private int mMaxUiWidth; + private int mMaxUiWidth = 0; final AppTransition mAppTransition; final AppTransitionController mAppTransitionController; @@ -361,9 +365,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Initial display metrics. int mInitialDisplayWidth = 0; int mInitialDisplayHeight = 0; - int mInitialDisplayDensity = 0; float mInitialPhysicalXDpi = 0.0f; float mInitialPhysicalYDpi = 0.0f; + // The physical density of the display + int mInitialDisplayDensity = 0; private Point mPhysicalDisplaySize; @@ -701,10 +706,33 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private boolean mInEnsureActivitiesVisible = false; - // Used to indicate that the movement of child tasks to top will not move the display to top as - // well and thus won't change the top resumed / focused record + /** + * Used to indicate that the movement of child tasks to top will not move the display to top as + * well and thus won't change the top resumed / focused record + */ boolean mDontMoveToTop; + /** Used for windows that want to keep the screen awake. */ + private PowerManager.WakeLock mHoldScreenWakeLock; + + /** The current window causing mHoldScreenWakeLock to be held. */ + private WindowState mHoldScreenWindow; + + /** + * Used during updates to temporarily store what will become the next value for + * mHoldScreenWindow. + */ + private WindowState mTmpHoldScreenWindow; + + /** Last window that obscures all windows below. */ + private WindowState mObscuringWindow; + + /** Last window which obscured a window holding the screen locked. */ + private WindowState mLastWakeLockObscuringWindow; + + /** Last window to hold the screen locked. */ + private WindowState mLastWakeLockHoldingWindow; + /** * The helper of policy controller. * @@ -917,7 +945,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (isDisplayed && w.isObscuringDisplay()) { // This window completely covers everything behind it, so we want to leave all // of them as undimmed (for performance reasons). - root.mObscuringWindow = w; + mObscuringWindow = w; mTmpApplySurfaceChangesTransactionState.obscured = true; } @@ -931,6 +959,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } if (w.mHasSurface && isDisplayed) { + if ((w.mAttrs.flags & FLAG_KEEP_SCREEN_ON) != 0) { + mTmpHoldScreenWindow = w; + } else if (w == mLastWakeLockHoldingWindow) { + ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, + "handleNotObscuredLocked: %s was holding screen wakelock but no longer " + + "has FLAG_KEEP_SCREEN_ON!!! called by%s", + w, Debug.getCallers(10)); + } + final int type = w.mAttrs.type; if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR @@ -1038,6 +1075,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCurrentUniqueDisplayId = display.getUniqueId(); mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer; mWallpaperController = new WallpaperController(mWmService, this); + mWallpaperController.resetLargestDisplay(display); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp @@ -1050,6 +1088,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); + mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, + TAG_WM + "/displayId:" + mDisplayId, mDisplayId); + mHoldScreenWakeLock.setReferenceCounted(false); + mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); @@ -1110,6 +1153,37 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); } + private void beginHoldScreenUpdate() { + mTmpHoldScreenWindow = null; + mObscuringWindow = null; + } + + private void finishHoldScreenUpdate() { + final boolean hold = mTmpHoldScreenWindow != null; + if (hold && mTmpHoldScreenWindow != mHoldScreenWindow) { + mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid)); + } + mHoldScreenWindow = mTmpHoldScreenWindow; + mTmpHoldScreenWindow = null; + + final boolean state = mHoldScreenWakeLock.isHeld(); + if (hold != state) { + if (hold) { + ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Acquiring screen wakelock due to %s", + mHoldScreenWindow); + mLastWakeLockHoldingWindow = mHoldScreenWindow; + mLastWakeLockObscuringWindow = null; + mHoldScreenWakeLock.acquire(); + } else { + ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by %s", + mObscuringWindow); + mLastWakeLockHoldingWindow = null; + mLastWakeLockObscuringWindow = mObscuringWindow; + mHoldScreenWakeLock.release(); + } + } + } + @Override void migrateToNewSurfaceControl(Transaction t) { t.remove(mSurfaceControl); @@ -2555,6 +2629,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mCurrentOverrideConfigurationChanges; } + /** + * @return The initial display density. This is constrained by config_maxUIWidth. + */ + int getInitialDisplayDensity() { + int density = mInitialDisplayDensity; + if (mMaxUiWidth > 0 && mInitialDisplayWidth > mMaxUiWidth) { + density = (int) ((density * mMaxUiWidth) / (float) mInitialDisplayWidth); + } + return density; + } + @Override public void onConfigurationChanged(Configuration newParentConfig) { final int lastOrientation = getConfiguration().orientation; @@ -2883,7 +2968,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * so only need to configure display. */ void setForcedDensity(int density, int userId) { - mIsDensityForced = density != mInitialDisplayDensity; + mIsDensityForced = density != getInitialDisplayDensity(); final boolean updateCurrent = userId == UserHandle.USER_CURRENT; if (mWmService.mCurrentUserId == userId || updateCurrent) { mBaseDisplayDensity = density; @@ -2894,7 +2979,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return; } - if (density == mInitialDisplayDensity) { + if (density == getInitialDisplayDensity()) { density = 0; } mWmService.mDisplayWindowSettings.setForcedDensity(this, density, userId); @@ -3199,6 +3284,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId); mRootWindowContainer.mTaskSupervisor .getKeyguardController().onDisplayRemoved(mDisplayId); + mWallpaperController.resetLargestDisplay(mDisplay); } finally { mDisplayReady = false; } @@ -3456,6 +3542,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } pw.println(); + pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow); + pw.println(); + pw.print(prefix + "mObscuringWindow="); pw.print(mObscuringWindow); + pw.println(); + pw.print(prefix + "mLastWakeLockHoldingWindow="); pw.print(mLastWakeLockHoldingWindow); + pw.println(); + pw.print(prefix + "mLastWakeLockObscuringWindow="); + pw.println(mLastWakeLockObscuringWindow); + + pw.println(); mWallpaperController.dump(pw, " "); if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() > 0) { @@ -4675,6 +4771,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void applySurfaceChangesTransaction() { final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked; + beginHoldScreenUpdate(); + mTmpUpdateAllDrawn.clear(); if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner", @@ -4750,6 +4848,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // can now be shown. activity.updateAllDrawn(); } + + finishHoldScreenUpdate(); } private void getBounds(Rect out, @Rotation int rotation) { @@ -5769,6 +5869,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp updateRecording(); } } + // Notify wallpaper controller of any size changes. + mWallpaperController.resetLargestDisplay(mDisplay); // Dispatch pending Configuration to WindowContext if the associated display changes to // un-suspended state from suspended. if (isSuspendedState(lastDisplayState) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 508e6dc77a61..d4eac8d3ad4b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -362,6 +362,7 @@ public class DisplayPolicy { private WindowState mTopFullscreenOpaqueWindowState; private boolean mTopIsFullscreen; private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED; + private boolean mForceConsumeSystemBars; private boolean mForceShowSystemBars; private boolean mShowingDream; @@ -1561,6 +1562,13 @@ public class DisplayPolicy { } /** + * @return true if the system bars are forced to be consumed + */ + public boolean areSystemBarsForcedConsumedLw() { + return mForceConsumeSystemBars; + } + + /** * @return true if the system bars are forced to stay visible */ public boolean areSystemBarsForcedShownLw() { @@ -2462,6 +2470,10 @@ public class DisplayPolicy { // We need to force showing system bars when the multi-window or freeform root task is // visible. mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible; + // We need to force the consumption of the system bars if they are force shown or if they + // are controlled by a remote insets controller. + mForceConsumeSystemBars = mForceShowSystemBars + || mDisplayContent.getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win); mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); final boolean topAppHidesStatusBar = topAppHidesStatusBar(); diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 11bcd8ca3681..9462d4f7829d 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -276,7 +276,7 @@ class DisplayWindowSettings { final int width = hasSizeOverride ? settings.mForcedWidth : dc.mInitialDisplayWidth; final int height = hasSizeOverride ? settings.mForcedHeight : dc.mInitialDisplayHeight; final int density = hasDensityOverride ? settings.mForcedDensity - : dc.mInitialDisplayDensity; + : dc.getInitialDisplayDensity(); dc.updateBaseDisplayMetrics(width, height, density, dc.mBaseDisplayPhysicalXDpi, dc.mBaseDisplayPhysicalYDpi); diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java index 3f6fb622481f..e3a2065838d1 100644 --- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -22,6 +22,8 @@ import android.util.Slog; import android.util.SparseArray; import android.view.DisplayInfo; +import java.util.ArrayList; +import java.util.List; import java.util.Set; /** @@ -52,19 +54,21 @@ public class PossibleDisplayInfoMapper { /** - * Returns, for the given displayId, a set of display infos. Set contains each supported device - * state. + * Returns, for the given displayId, a list of unique display infos. List contains each + * supported device state. + * <p>List contents are guaranteed to be unique, but returned as a list rather than a set to + * minimize copies needed to make an iteraable data structure. */ - public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) { + public List<DisplayInfo> getPossibleDisplayInfos(int displayId) { // Update display infos before returning, since any cached values would have been removed // in response to any display event. This model avoids re-computing the cache for every // display change event (which occurs extremely frequently in the normal usage of the // device). updatePossibleDisplayInfos(displayId); if (!mDisplayInfos.contains(displayId)) { - return new ArraySet<>(); + return new ArrayList<>(); } - return Set.copyOf(mDisplayInfos.get(displayId)); + return List.copyOf(mDisplayInfos.get(displayId)); } /** diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 077f8b55e5e6..b3f35487f3a5 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -28,7 +28,6 @@ import static android.content.res.Configuration.EMPTY; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; @@ -176,15 +175,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private Object mLastWindowFreezeSource = null; - private Session mHoldScreen = null; private float mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; private long mUserActivityTimeout = -1; private boolean mUpdateRotation = false; - // Following variables are for debugging screen wakelock only. - // Last window that requires screen wakelock - WindowState mHoldScreenWindow = null; - // Last window that obscures all windows below - WindowState mObscuringWindow = null; // Only set while traversing the default display based on its content. // Affects the behavior of mirroring on secondary displays. private boolean mObscureApplicationContentOnSecondaryDisplays = false; @@ -801,7 +794,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); } - mHoldScreen = null; mScreenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT; mUserActivityTimeout = -1; mObscureApplicationContentOnSecondaryDisplays = false; @@ -914,7 +906,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - mWmService.setHoldScreenLocked(mHoldScreen); if (!mWmService.mDisplayFrozen) { final float brightnessOverride = mScreenBrightnessOverride < PowerManager.BRIGHTNESS_MIN || mScreenBrightnessOverride > PowerManager.BRIGHTNESS_MAX @@ -994,9 +985,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } private void applySurfaceChangesTransaction() { - mHoldScreenWindow = null; - mObscuringWindow = null; - // TODO(multi-display): Support these features on secondary screens. final DisplayContent defaultDc = mDefaultDisplay; final DisplayInfo defaultInfo = defaultDc.getDisplayInfo(); @@ -1071,15 +1059,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } if (w.mHasSurface && canBeSeen) { - if ((attrFlags & FLAG_KEEP_SCREEN_ON) != 0) { - mHoldScreen = w.mSession; - mHoldScreenWindow = w; - } else if (w == mWmService.mLastWakeLockHoldingWindow) { - ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, - "handleNotObscuredLocked: %s was holding screen wakelock but no longer " - + "has FLAG_KEEP_SCREEN_ON!!! called by%s", - w, Debug.getCallers(10)); - } if (!syswin && w.mAttrs.screenBrightness >= 0 && Float.isNaN(mScreenBrightnessOverride)) { mScreenBrightnessOverride = w.mAttrs.screenBrightness; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index de135a34c3df..659e37bb3de7 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -60,10 +60,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; -import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -4973,23 +4970,17 @@ class Task extends TaskFragment { dc.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(r); } else { - int transit = TRANSIT_OLD_ACTIVITY_OPEN; - if (newTask) { - if (r.mLaunchTaskBehind) { - transit = TRANSIT_OLD_TASK_OPEN_BEHIND; - } else { - // If a new task is being launched, then mark the existing top activity as - // supporting picture-in-picture while pausing only if the starting activity - // would not be considered an overlay on top of the current activity - // (eg. not fullscreen, or the assistant) - enableEnterPipOnTaskSwitch(pipCandidate, - null /* toFrontTask */, r, options); - transit = TRANSIT_OLD_TASK_OPEN; - } - } dc.prepareAppTransition(TRANSIT_OPEN); mTaskSupervisor.mNoAnimActivities.remove(r); } + if (newTask && !r.mLaunchTaskBehind) { + // If a new task is being launched, then mark the existing top activity as + // supporting picture-in-picture while pausing only if the starting activity + // would not be considered an overlay on top of the current activity + // (eg. not fullscreen, or the assistant) + enableEnterPipOnTaskSwitch(pipCandidate, + null /* toFrontTask */, r, options); + } boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index d8a054cf45fa..8c037a7390b1 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -45,6 +45,7 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationDefinition; +import android.view.WindowManager; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; @@ -484,16 +485,31 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } @Override - public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer, - @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) { + public void onTransactionHandled(@NonNull IBinder transactionToken, + @NonNull WindowContainerTransaction wct, + @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) { + // Keep the calling identity to avoid unsecure change. synchronized (mGlobalLock) { - // Keep the calling identity to avoid unsecure change. - mWindowOrganizerController.applyTransaction(wct); - final TaskFragmentOrganizerState state = validateAndGetState(organizer); + applyTransaction(wct, transitionType, shouldApplyIndependently); + final TaskFragmentOrganizerState state = validateAndGetState( + wct.getTaskFragmentOrganizer()); state.onTransactionFinished(transactionToken); } } + @Override + public void applyTransaction(@NonNull WindowContainerTransaction wct, + @WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) { + // Keep the calling identity to avoid unsecure change. + synchronized (mGlobalLock) { + if (wct.isEmpty()) { + return; + } + mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType, + shouldApplyIndependently); + } + } + /** * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode. diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java index b8d2febd5dd4..09fd90030f0c 100644 --- a/services/core/java/com/android/server/wm/TaskPersister.java +++ b/services/core/java/com/android/server/wm/TaskPersister.java @@ -53,6 +53,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -424,7 +425,7 @@ public class TaskPersister implements PersisterQueue.Listener { private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds + - " files=" + files); + " files=" + Arrays.toString(files)); if (files == null) { Slog.e(TAG, "File error accessing recents directory (directory doesn't exist?)."); return; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 6245005606d7..d652b8e0ab32 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -35,7 +36,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT; +import android.annotation.Nullable; import android.graphics.Bitmap; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.Debug; @@ -45,6 +48,8 @@ import android.os.SystemClock; import android.util.ArraySet; import android.util.MathUtils; import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -56,6 +61,7 @@ import com.android.internal.util.ToBooleanFunction; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -113,8 +119,12 @@ class WallpaperController { */ private WindowState mTmpTopWallpaper; + @Nullable private Point mLargestDisplaySize = null; + private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); + private boolean mShouldOffsetWallpaperCenter; + private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> { if ((w.mAttrs.type == TYPE_WALLPAPER)) { if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) { @@ -241,6 +251,38 @@ class WallpaperController { mDisplayContent = displayContent; mMaxWallpaperScale = service.mContext.getResources() .getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale); + mShouldOffsetWallpaperCenter = service.mContext.getResources() + .getBoolean( + com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); + } + + void resetLargestDisplay(Display display) { + if (display != null && display.getType() == Display.TYPE_INTERNAL) { + mLargestDisplaySize = null; + } + } + + @VisibleForTesting void setShouldOffsetWallpaperCenter(boolean shouldOffset) { + mShouldOffsetWallpaperCenter = shouldOffset; + } + + @Nullable private Point findLargestDisplaySize() { + if (!mShouldOffsetWallpaperCenter) { + return null; + } + Point largestDisplaySize = new Point(); + List<DisplayInfo> possibleDisplayInfo = + mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY); + for (int i = 0; i < possibleDisplayInfo.size(); i++) { + DisplayInfo displayInfo = possibleDisplayInfo.get(i); + if (displayInfo.type == Display.TYPE_INTERNAL + && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight) + > Math.max(largestDisplaySize.x, largestDisplaySize.y)) { + largestDisplaySize.set(displayInfo.logicalWidth, + displayInfo.logicalHeight); + } + } + return largestDisplaySize; } WindowState getWallpaperTarget() { @@ -327,24 +369,44 @@ class WallpaperController { } boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) { - final Rect bounds = wallpaperWin.getLastReportedBounds(); - final int dw = bounds.width(); - final int dh = bounds.height(); - - int xOffset = 0; - int yOffset = 0; + // Size of the display the wallpaper is rendered on. + final Rect lastWallpaperBounds = wallpaperWin.getLastReportedBounds(); + // Full size of the wallpaper (usually larger than bounds above to parallax scroll when + // swiping through Launcher pages). + final Rect wallpaperFrame = wallpaperWin.getFrame(); + + int newXOffset = 0; + int newYOffset = 0; boolean rawChanged = false; // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to // match the behavior of most Launchers float defaultWallpaperX = wallpaperWin.isRtl() ? 1f : 0f; + // "Wallpaper X" is the previous x-offset of the wallpaper (in a 0 to 1 scale). + // The 0 to 1 scale is because the "length" varies depending on how many home screens you + // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for + // LTR, and the opposite for RTL). float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX; + // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen + // when scrolling. float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; - int availw = wallpaperWin.getFrame().right - wallpaperWin.getFrame().left - dw; + // Difference between width of wallpaper image, and the last size of the wallpaper. + // This is the horizontal surplus from the prior configuration. + int availw = wallpaperFrame.width() - lastWallpaperBounds.width(); + + int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds, + wallpaperWin.isRtl()); + availw -= displayOffset; int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0; if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn + // always starting from the left of the screen). offset += mLastWallpaperDisplayOffsetX; + } else if (!wallpaperWin.isRtl()) { + // In RTL the offset is calculated so that the wallpaper ends up right aligned (see + // offset above). + offset -= displayOffset; } - xOffset = offset; + newXOffset = offset; if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) { wallpaperWin.mWallpaperX = wpx; @@ -354,12 +416,13 @@ class WallpaperController { float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; - int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top - dh; + int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top + - lastWallpaperBounds.height(); offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0; if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { offset += mLastWallpaperDisplayOffsetY; } - yOffset = offset; + newYOffset = offset; if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) { wallpaperWin.mWallpaperY = wpy; @@ -372,7 +435,7 @@ class WallpaperController { rawChanged = true; } - boolean changed = wallpaperWin.setWallpaperOffset(xOffset, yOffset, + boolean changed = wallpaperWin.setWallpaperOffset(newXOffset, newYOffset, wallpaperWin.mShouldScaleWallpaper ? zoomOutToScale(wallpaperWin.mWallpaperZoomOut) : 1); @@ -419,6 +482,52 @@ class WallpaperController { return changed; } + /** + * Get an extra offset if needed ({@link #mShouldOffsetWallpaperCenter} = true, typically on + * multiple display devices) so that the wallpaper in a smaller display ends up centered at the + * same position as in the largest display of the device. + * + * Note that the wallpaper has already been cropped when set by the user, so these calculations + * apply to the image size for the display the wallpaper was set for. + * + * @param availWidth width available for the wallpaper offset in the current display + * @param displayFrame size of the "display" (parent frame) + * @param isRtl whether we're in an RTL configuration + * @return an offset to apply to the width, or 0 if the current configuration doesn't require + * any adjustment (either @link #mShouldOffsetWallpaperCenter} is false or we're on the largest + * display). + */ + private int getDisplayWidthOffset(int availWidth, Rect displayFrame, boolean isRtl) { + if (!mShouldOffsetWallpaperCenter) { + return 0; + } + if (mLargestDisplaySize == null) { + mLargestDisplaySize = findLargestDisplaySize(); + } + if (mLargestDisplaySize == null) { + return 0; + } + // Page width is the width of a Launcher "page", for pagination when swiping right. + int pageWidth = displayFrame.width(); + // Only need offset if the current size is different from the largest display, and we're + // in a portrait configuration + if (mLargestDisplaySize.x != pageWidth && displayFrame.width() < displayFrame.height()) { + // The wallpaper will be scaled to fit the height of the wallpaper, so if the height + // of the displays are different, we need to account for that scaling when calculating + // the offset to the center + float sizeRatio = (float) displayFrame.height() / mLargestDisplaySize.y; + // Scale the width of the largest display to match the scale of the wallpaper size in + // the current display + int adjustedLargestWidth = Math.round(mLargestDisplaySize.x * sizeRatio); + // Finally, find the difference between the centers, taking into account that the + // size of the wallpaper frame could be smaller than the screen + return isRtl + ? adjustedLargestWidth - (adjustedLargestWidth + pageWidth) / 2 + : Math.min(adjustedLargestWidth - pageWidth, availWidth) / 2; + } + return 0; + } + void setWindowWallpaperPosition( WindowState window, float x, float y, float xStep, float yStep) { if (window.mWallpaperX != x || window.mWallpaperY != y) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8025cb296b32..236d0ddd067a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -103,7 +103,6 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; @@ -218,7 +217,6 @@ import android.os.SystemProperties; import android.os.SystemService; import android.os.Trace; import android.os.UserHandle; -import android.os.WorkSource; import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.service.vr.IVrManager; @@ -620,10 +618,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mBootAnimationStopped = false; long mBootWaitForWindowsStartTime = -1; - // Following variables are for debugging screen wakelock only. - WindowState mLastWakeLockHoldingWindow = null; - WindowState mLastWakeLockObscuringWindow = null; - /** Dump of the windows and app tokens at the time of the last ANR. Cleared after * LAST_ANR_LIFETIME_DURATION_MSECS */ String mLastANRState; @@ -991,10 +985,6 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mHasWideColorGamutSupport; private boolean mHasHdrSupport; - /** Who is holding the screen on. */ - private Session mHoldingScreenOn; - private PowerManager.WakeLock mHoldingScreenWakeLock; - /** Whether or not a layout can cause a wake up when theater mode is enabled. */ boolean mAllowTheaterModeWakeFromLayout; @@ -1326,10 +1316,6 @@ public class WindowManagerService extends IWindowManager.Stub mSettingsObserver = new SettingsObserver(); - mHoldingScreenWakeLock = mPowerManager.newWakeLock( - PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM); - mHoldingScreenWakeLock.setReferenceCounted(false); - mSurfaceAnimationRunner = new SurfaceAnimationRunner(mTransactionFactory, mPowerManagerInternal); @@ -1808,7 +1794,7 @@ public class WindowManagerService extends IWindowManager.Stub prepareNoneTransitionForRelaunching(activity); } - if (displayPolicy.areSystemBarsForcedShownLw()) { + if (displayPolicy.areSystemBarsForcedConsumedLw()) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; } @@ -2527,7 +2513,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mActivityRecord != null) { win.mActivityRecord.updateReportedVisibilityLocked(); } - if (displayPolicy.areSystemBarsForcedShownLw()) { + if (displayPolicy.areSystemBarsForcedConsumedLw()) { result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; } if (!win.isGoneForLayout()) { @@ -3591,8 +3577,8 @@ public class WindowManagerService extends IWindowManager.Stub // Otherwise, we'll update it when it's prepared. if (mDisplayReady) { final int forcedDensity = getForcedDisplayDensityForUserLocked(newUserId); - final int targetDensity = forcedDensity != 0 ? forcedDensity - : displayContent.mInitialDisplayDensity; + final int targetDensity = forcedDensity != 0 + ? forcedDensity : displayContent.getInitialDisplayDensity(); displayContent.setForcedDensity(targetDensity, UserHandle.USER_CURRENT); } } @@ -5803,7 +5789,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { - return displayContent.mInitialDisplayDensity; + return displayContent.getInitialDisplayDensity(); } } return -1; @@ -5858,7 +5844,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null) { - displayContent.setForcedDensity(displayContent.mInitialDisplayDensity, + displayContent.setForcedDensity(displayContent.getInitialDisplayDensity(), callingUserId); } } @@ -6013,34 +5999,6 @@ public class WindowManagerService extends IWindowManager.Stub }); } - void setHoldScreenLocked(final Session newHoldScreen) { - final boolean hold = newHoldScreen != null; - - if (hold && mHoldingScreenOn != newHoldScreen) { - mHoldingScreenWakeLock.setWorkSource(new WorkSource(newHoldScreen.mUid)); - } - mHoldingScreenOn = newHoldScreen; - - final boolean state = mHoldingScreenWakeLock.isHeld(); - if (hold != state) { - if (hold) { - ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Acquiring screen wakelock due to %s", - mRoot.mHoldScreenWindow); - mLastWakeLockHoldingWindow = mRoot.mHoldScreenWindow; - mLastWakeLockObscuringWindow = null; - mHoldingScreenWakeLock.acquire(); - mPolicy.keepScreenOnStartedLw(); - } else { - ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "Releasing screen wakelock, obscured by %s", - mRoot.mObscuringWindow); - mLastWakeLockHoldingWindow = null; - mLastWakeLockObscuringWindow = mRoot.mObscuringWindow; - mPolicy.keepScreenOnStoppedLw(); - mHoldingScreenWakeLock.release(); - } - } - } - void requestTraversal() { mWindowPlacerLocked.requestTraversal(); } @@ -6674,9 +6632,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(mLastFinishedFreezeSource); } pw.println(); - pw.print(" mLastWakeLockHoldingWindow=");pw.print(mLastWakeLockHoldingWindow); - pw.print(" mLastWakeLockObscuringWindow="); pw.print(mLastWakeLockObscuringWindow); - pw.println(); mInputManagerCallback.dump(pw, " "); mTaskSnapshotController.dump(pw, " "); @@ -8896,7 +8851,7 @@ public class WindowManagerService extends IWindowManager.Stub : overrideScale; outInsetsState.scale(1f / compatScale); } - return dc.getDisplayPolicy().areSystemBarsForcedShownLw(); + return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw(); } } finally { Binder.restoreCallingIdentity(origId); @@ -8916,13 +8871,19 @@ public class WindowManagerService extends IWindowManager.Stub } // Retrieve the DisplayInfo across all possible display layouts. - return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId)); + return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId); } } finally { Binder.restoreCallingIdentity(origId); } } + List<DisplayInfo> getPossibleDisplayInfoLocked(int displayId) { + // Retrieve the DisplayInfo for all possible rotations across all possible display + // layouts. + return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId); + } + /** * Returns {@code true} when the calling package is the recents component. */ diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 34a4bf1d9c2a..d34ad7d79695 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -29,6 +29,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; @@ -74,6 +75,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.RemoteAnimationAdapter; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; @@ -176,7 +178,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (t == null) { throw new IllegalArgumentException("Null transaction passed to applyTransaction"); } - enforceTaskPermission("applyTransaction()", t); + enforceTaskPermission("applyTransaction()"); final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { @@ -194,7 +196,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (t == null) { throw new IllegalArgumentException("Null transaction passed to applySyncTransaction"); } - enforceTaskPermission("applySyncTransaction()", t); + enforceTaskPermission("applySyncTransaction()"); final CallerInfo caller = new CallerInfo(); final long ident = Binder.clearCallingIdentity(); try { @@ -373,6 +375,87 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + /** + * Applies the {@link WindowContainerTransaction} as a request from + * {@link android.window.TaskFragmentOrganizer}. + * + * @param wct {@link WindowContainerTransaction} to apply. + * @param type {@link WindowManager.TransitionType} if it needs to start a new transition. + * @param shouldApplyIndependently If {@code true}, the {@code wct} will request a new + * transition, which will be queued until the sync engine is + * free if there is any other active sync. If {@code false}, + * the {@code wct} will be directly applied to the active sync. + */ + void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct, + @WindowManager.TransitionType int type, boolean shouldApplyIndependently) { + if (!isValidTransaction(wct)) { + return; + } + enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()", + Objects.requireNonNull(wct.getTaskFragmentOrganizer()), + Objects.requireNonNull(wct)); + final CallerInfo caller = new CallerInfo(); + final long ident = Binder.clearCallingIdentity(); + try { + if (mTransitionController.getTransitionPlayer() == null) { + // No need to worry about transition when Shell transition is not enabled. + applyTransaction(wct, -1 /* syncId */, null /* transition */, caller); + return; + } + + if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) { + // Sync is for either transition or applySyncTransaction(). We don't support + // multiple sync at the same time because it may cause conflict. + // Create a new transition when there is no active sync to collect the changes. + final Transition transition = mTransitionController.createTransition(type); + applyTransaction(wct, -1 /* syncId */, transition, caller); + mTransitionController.requestStartTransition(transition, null /* startTask */, + null /* remoteTransition */, null /* displayChange */); + return; + } + + if (!shouldApplyIndependently) { + // Although there is an active sync, we want to apply the transaction now. + if (!mTransitionController.isCollecting()) { + // This should rarely happen, and we should try to avoid using + // {@link #applySyncTransaction} with Shell transition. + // We still want to apply and merge the transaction to the active sync + // because {@code shouldApplyIndependently} is {@code false}. + ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "TaskFragmentTransaction changes are not collected in transition" + + " because there is an ongoing sync for" + + " applySyncTransaction()."); + } + // TODO(b/207070762) make sure changes are all collected. + applyTransaction(wct, -1 /* syncId */, null /* transition */, caller); + return; + } + + // It is ok to queue the WCT until the sync engine is free. + final Transition nextTransition = new Transition(type, 0 /* flags */, + mTransitionController, mService.mWindowManager.mSyncEngine); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Creating Pending Transition for TaskFragment: %s", nextTransition); + mService.mWindowManager.mSyncEngine.queueSyncSet( + // Make sure to collect immediately to prevent another transition + // from sneaking in before it. Note: moveToCollecting internally + // calls startSyncSet. + () -> mTransitionController.moveToCollecting(nextTransition), + () -> { + if (isValidTransaction(wct)) { + applyTransaction(wct, -1 /*syncId*/, nextTransition, caller); + mTransitionController.requestStartTransition(nextTransition, + null /* startTask */, null /* remoteTransition */, + null /* displayChange */); + } else { + nextTransition.abort(); + } + }); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, @Nullable Transition transition, @NonNull CallerInfo caller) { applyTransaction(t, syncId, transition, caller, null /* finishTransition */); @@ -387,12 +470,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId, @Nullable Transition transition, @NonNull CallerInfo caller, @Nullable Transition finishTransition) { - if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController - .isOrganizerRegistered(t.getTaskFragmentOrganizer())) { - Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer() - + " is no longer registered"); - return; - } int effects = 0; ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); @@ -703,6 +780,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) { final int type = hop.getType(); switch (type) { + case HIERARCHY_OP_TYPE_REMOVE_TASK: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); + final Task task = wc != null ? wc.asTask() : null; + task.remove(true, "Applying remove task Hierarchy Op"); + break; + } case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: { final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); final Task task = wc != null ? wc.asTask() : null; @@ -1488,25 +1571,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.enforceTaskPermission(func); } - private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) { - if (t == null || t.getTaskFragmentOrganizer() == null) { - enforceTaskPermission(func); - return; + private boolean isValidTransaction(@NonNull WindowContainerTransaction t) { + if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController + .isOrganizerRegistered(t.getTaskFragmentOrganizer())) { + // Transaction from an unregistered organizer should not be applied. This can happen + // when the organizer process died before the transaction is applied. + Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer() + + " is no longer registered"); + return false; } - - // Apps may not have the permission to manage Tasks, but we are allowing apps to manage - // TaskFragments belonging to their own Task. - enforceOperationsAllowedForTaskFragmentOrganizer(func, t); + return true; } /** * Makes sure that the transaction only contains operations that are allowed for the * {@link WindowContainerTransaction#getTaskFragmentOrganizer()}. */ - private void enforceOperationsAllowedForTaskFragmentOrganizer( - String func, WindowContainerTransaction t) { - final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer(); - + private void enforceTaskFragmentOrganizerPermission(@NonNull String func, + @NonNull ITaskFragmentOrganizer organizer, @NonNull WindowContainerTransaction t) { // Configuration changes final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = t.getChanges().entrySet().iterator(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 346930311068..2e7e78db9e05 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3917,7 +3917,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean forceRelayout = syncWithBuffers || isDragResizeChanged; final DisplayContent displayContent = getDisplayContent(); final boolean alwaysConsumeSystemBars = - displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(); + displayContent.getDisplayPolicy().areSystemBarsForcedConsumedLw(); final int displayId = displayContent.getDisplayId(); if (isDragResizeChanged) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 2ee5fb01efb3..aa589024fdc6 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -228,7 +228,5 @@ class WindowSurfacePlacer { public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "mTraversalScheduled=" + mTraversalScheduled); - pw.println(prefix + "mHoldScreenWindow=" + mService.mRoot.mHoldScreenWindow); - pw.println(prefix + "mObscuringWindow=" + mService.mRoot.mObscuringWindow); } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 40789964f4e1..5d6ffd8b8ead 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -37,6 +37,7 @@ cc_library_static { "com_android_server_ConsumerIrService.cpp", "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", + "com_android_server_display_DisplayControl.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", @@ -89,6 +90,7 @@ cc_library_static { cc_defaults { name: "libservices.core-libs", + defaults: ["android.hardware.graphics.common-ndk_shared"], shared_libs: [ "libadb_pairing_server", "libadb_pairing_connection", @@ -158,7 +160,6 @@ cc_defaults { "android.hardware.graphics.bufferqueue@1.0", "android.hardware.graphics.bufferqueue@2.0", "android.hardware.graphics.common@1.2", - "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.mapper@4.0", "android.hardware.input.processor-V1-ndk", "android.hardware.ir@1.0", diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp new file mode 100644 index 000000000000..61f2b1411a0b --- /dev/null +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android_util_Binder.h> +#include <gui/SurfaceComposerClient.h> +#include <jni.h> +#include <nativehelper/ScopedUtfChars.h> + +namespace android { + +static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) { + ScopedUtfChars name(env, nameObj); + sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure))); + return javaObjectForIBinder(env, token); +} + +static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + if (token == NULL) return; + SurfaceComposerClient::destroyDisplay(token); +} + +// ---------------------------------------------------------------------------- + +static const JNINativeMethod sDisplayMethods[] = { + // clang-format off + {"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;", + (void*)nativeCreateDisplay }, + {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", + (void*)nativeDestroyDisplay }, + // clang-format on +}; + +int register_com_android_server_display_DisplayControl(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/display/DisplayControl", + sDisplayMethods, NELEM(sDisplayMethods)); +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 0cd949457cd1..00bef0935308 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -64,6 +64,7 @@ int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); +int register_com_android_server_display_DisplayControl(JNIEnv* env); }; using namespace android; @@ -120,5 +121,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_companion_virtual_InputController(env); register_android_server_app_GameManagerService(env); register_com_android_server_wm_TaskFpsCallbackController(env); + register_com_android_server_display_DisplayControl(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fbaf1ce25b85..7b941d1aa19b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5760,36 +5760,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new ParcelableGranteeMap(result); } - /** - * Enforce one the following conditions are met: - * (1) The device has a Device Owner, and one of the following holds: - * (1.1) The caller is the Device Owner - * (1.2) The caller is another app in the same user as the device owner, AND - * The caller is the delegated certificate installer. - * (1.3) The caller is a Profile Owner and the calling user is affiliated. - * (2) The user has a profile owner, AND: - * (2.1) The profile owner has been granted access to Device IDs and one of the following - * holds: - * (2.1.1) The caller is the profile owner. - * (2.1.2) The caller is from another app in the same user as the profile owner, AND - * (2.1.2.1) The caller is the delegated cert installer. - * - * For the device owner case, simply check that the caller is the device owner or the - * delegated certificate installer. - * - * For the profile owner case, first check that the caller is the profile owner or can - * manage the DELEGATION_CERT_INSTALL scope. - * If that check succeeds, ensure the profile owner was granted access to device - * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed - * access to device identifiers in this case as part of the delegation. - */ - @VisibleForTesting - public void enforceCallerCanRequestDeviceIdAttestation(CallerIdentity caller) - throws SecurityException { - Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked(caller.getPackageName(), - caller.getUid())); - } - @VisibleForTesting public static int[] translateIdAttestationFlags( int idAttestationFlags) { @@ -5844,8 +5814,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) { - // TODO: replace enforce methods - enforceCallerCanRequestDeviceIdAttestation(caller); + Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked( + caller.getPackageName(), caller.getUid())); enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() @@ -9376,21 +9346,36 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** - * Check if caller is device owner, delegate cert installer or profile owner of - * affiliated user. Or if caller is profile owner for a specified user or delegate cert - * installer on an organization-owned device. + * Check if one the following conditions hold: + * (1) The device has a Device Owner, and one of the following holds: + * (1.1) The caller is the Device Owner + * (1.2) The caller is another app in the same user as the device owner, AND + * The caller is the delegated certificate installer. + * (1.3) The caller is a Profile Owner and the calling user is affiliated. + * (2) The user has a profile owner, AND: + * (2.1) The profile owner has been granted access to Device IDs and one of the following + * holds: + * (2.1.1) The caller is the profile owner. + * (2.1.2) The caller is from another app in the same user as the profile owner, AND + * the caller is the delegated cert installer. + * + * For the device owner case, simply check that the caller is the device owner or the + * delegated certificate installer. + * + * For the profile owner case, first check that the caller is the profile owner or can + * manage the DELEGATION_CERT_INSTALL scope. + * If that check succeeds, ensure the profile owner was granted access to device + * identifiers. The grant is transitive: The delegated cert installer is implicitly allowed + * access to device identifiers in this case as part of the delegation. */ - private boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { - // Is the caller a device owner, delegate cert installer or profile owner of an - // affiliated user. + @VisibleForTesting + boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { ComponentName deviceOwner = getDeviceOwnerComponent(true); if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { return true; } final int userId = UserHandle.getUserId(uid); - // Is the caller the profile owner for the specified user, or delegate cert installer on an - // organization-owned device. ComponentName profileOwner = getProfileOwnerAsUser(userId); final boolean isCallerProfileOwnerOrDelegate = profileOwner != null && (profileOwner.getPackageName().equals(packageName) diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index d3222901db1e..3c6866205fda 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -205,6 +205,7 @@ public final class ProfcollectForwardingService extends SystemService { .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setPeriodic(BG_PROCESS_PERIOD) + .setPriority(JobInfo.PRIORITY_MIN) .build()); } diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp index f454ac7e9e4b..f1dc1fa1a108 100644 --- a/services/tests/mockingservicestests/jni/Android.bp +++ b/services/tests/mockingservicestests/jni/Android.bp @@ -10,6 +10,8 @@ package { cc_library_shared { name: "libmockingservicestestjni", + defaults: ["android.hardware.graphics.common-ndk_shared"], + cflags: [ "-Wall", "-Werror", @@ -48,7 +50,6 @@ cc_library_shared { "android.hardware.graphics.bufferqueue@1.0", "android.hardware.graphics.bufferqueue@2.0", "android.hardware.graphics.common@1.2", - "android.hardware.graphics.common-V3-ndk", "android.hardware.graphics.mapper@4.0", "android.hidl.token@1.0-utils", ], diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java new file mode 100644 index 000000000000..130b02d3989e --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.app.BroadcastOptions; +import android.app.IApplicationThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.LocalServices; +import com.android.server.am.ActivityManagerService.Injector; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Common tests for {@link BroadcastQueue} implementations. + */ +@MediumTest +@RunWith(Parameterized.class) +public class BroadcastQueueTest { + private static final String TAG = "BroadcastQueueTest"; + + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); + + private final Impl mImpl; + + private enum Impl { + DEFAULT + } + + private Context mContext; + private HandlerThread mHandlerThread; + + @Mock + private AppOpsService mAppOpsService; + @Mock + private PackageManagerInternal mPackageManagerInt; + + private ActivityManagerService mAms; + private BroadcastQueue mQueue; + + @Parameters(name = "impl={0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { {Impl.DEFAULT} }); + } + + public BroadcastQueueTest(Impl impl) { + mImpl = impl; + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); + + final ActivityManagerService realAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); + realAms.mPackageManagerInt = mPackageManagerInt; + mAms = spy(realAms); + + final BroadcastConstants constants = new BroadcastConstants( + Settings.Global.BROADCAST_FG_CONSTANTS); + final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) { + public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) { + return false; + } + public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) { + return false; + } + }; + + if (mImpl == Impl.DEFAULT) { + mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG, + constants, emptySkipPolicy, false); + } else { + throw new UnsupportedOperationException(); + } + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + } + + private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception { + final ProcessRecord r = new ProcessRecord(mAms, makeApplicationInfo(packageName), null, + getUidForPackage(packageName)); + final IApplicationThread thread = mock(IApplicationThread.class); + final IBinder threadBinder = new Binder(); + doReturn(threadBinder).when(thread).asBinder(); + r.makeActive(thread, mAms.mProcessStats); + + doReturn(r).when(mAms).getProcessRecordLocked(eq(r.info.processName), eq(r.info.uid)); + + doAnswer((invocation) -> { + Log.v(TAG, "Delivering finishReceiverLocked() for " + + Arrays.toString(invocation.getArguments())); + mQueue.finishReceiverLocked(threadBinder, Activity.RESULT_OK, + null, null, false, false); + return null; + }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(), + anyInt(), anyInt()); + + return r; + } + + private ApplicationInfo makeApplicationInfo(String packageName) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.uid = getUidForPackage(packageName); + return ai; + } + + private ResolveInfo makeManifestReceiver(String packageName, String name) { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = packageName; + ri.activityInfo.name = name; + ri.activityInfo.applicationInfo = makeApplicationInfo(packageName); + return ri; + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, + List receivers) { + return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), receivers); + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, + BroadcastOptions options, List receivers) { + return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null, + callerApp.getPid(), callerApp.info.uid, false, null, null, null, null, + AppOpsManager.OP_NONE, options, receivers, null, Activity.RESULT_OK, null, null, + false, false, false, UserHandle.USER_SYSTEM, false, null, false, null); + } + + private ArgumentMatcher<Intent> filterEqualsIgnoringComponent(Intent intent) { + final Intent intentClean = new Intent(intent); + intentClean.setComponent(null); + return (test) -> { + final Intent testClean = new Intent(test); + testClean.setComponent(null); + return intentClean.filterEquals(testClean); + }; + } + + private void waitForIdle() throws Exception { + for (int i = 0; i < 100; i++) { + if (mQueue.isIdle()) break; + SystemClock.sleep(100); + } + assertTrue(mQueue.isIdle()); + } + + private static final String PACKAGE_RED = "com.example.red"; + private static final String PACKAGE_GREEN = "com.example.green"; + private static final String PACKAGE_BLUE = "com.example.blue"; + + private static final String CLASS_RED = "com.example.red.Red"; + private static final String CLASS_GREEN = "com.example.green.Green"; + private static final String CLASS_BLUE = "com.example.blue.Blue"; + + private static int getUidForPackage(String packageName) { + switch (packageName) { + case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1; + case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2; + case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3; + default: throw new IllegalArgumentException(); + } + } + + @Test + public void testSimple_Manifest_Warm() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))); + + waitForIdle(); + verify(receiverApp.getThread()).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(), + any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + } + + @Test + public void testSimple_Manifest_Warm_Multiple() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); + + waitForIdle(); + verify(receiverGreenApp.getThread()).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(timezone)), any(), any(), anyInt(), any(), + any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(receiverBlueApp.getThread()).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(timezone)), any(), any(), anyInt(), any(), + any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + verify(receiverBlueApp.getThread()).scheduleReceiver( + argThat(filterEqualsIgnoringComponent(airplane)), any(), any(), anyInt(), any(), + any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt()); + } + + // TODO: verify registered receiver in warm app + // TODO: verify manifest receiver in cold app + + // TODO: verify mixing multiple manifest and registered receivers of same broadcast + // TODO: verify delivery of 3 distinct broadcasts +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java index 9d269719db64..fb9cbb00255c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -61,7 +61,6 @@ import org.mockito.quality.Strictness; import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -1029,44 +1028,4 @@ public class DexManagerTests { return mPackageInfo.applicationInfo.splitSourceDirs[length - 1]; } } - - private boolean shouldPackageRunOob(boolean isDefaultEnabled, String whitelist, - Collection<String> packageNamesInSameProcess) { - return DexManager.isPackageSelectedToRunOobInternal( - isDefaultEnabled, whitelist, packageNamesInSameProcess); - } - - @Test - public void testOobPackageSelectionDefault() { - // Feature is off by default, not overriden - assertFalse(shouldPackageRunOob(false, "ALL", null)); - } - - @Test - public void testOobPackageSelectionWhitelist() { - // Various allowlist of apps to run in OOB mode. - final String kWhitelistApp0 = "com.priv.app0"; - final String kWhitelistApp1 = "com.priv.app1"; - final String kWhitelistApp2 = "com.priv.app2"; - final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2"; - - // Packages that shares the targeting process. - final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2"); - - // Feature is off, allowlist does not matter - assertFalse(shouldPackageRunOob(false, kWhitelistApp0, runningPackages)); - assertFalse(shouldPackageRunOob(false, kWhitelistApp1, runningPackages)); - assertFalse(shouldPackageRunOob(false, "", runningPackages)); - assertFalse(shouldPackageRunOob(false, "ALL", runningPackages)); - - // Feature is on, app not in allowlist - assertFalse(shouldPackageRunOob(true, kWhitelistApp0, runningPackages)); - assertFalse(shouldPackageRunOob(true, "", runningPackages)); - - // Feature is on, app in allowlist - assertTrue(shouldPackageRunOob(true, kWhitelistApp1, runningPackages)); - assertTrue(shouldPackageRunOob(true, kWhitelistApp2, runningPackages)); - assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, runningPackages)); - assertTrue(shouldPackageRunOob(true, "ALL", runningPackages)); - } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 59b69f9ffebf..233caf9c7761 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -837,7 +837,7 @@ public class MotionEventInjectorTest { @Override public void describeTo(Description description) { - description.appendText("Contains points " + points); + description.appendText("Contains points " + Arrays.toString(points)); } }; } diff --git a/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java index 6bb494d3a92a..c75abf84fb89 100644 --- a/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ambientcontext/AmbientContextManagerServiceTest.java @@ -21,8 +21,8 @@ import static com.google.common.truth.Truth.assertThat; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.IAmbientContextObserver; import android.content.Intent; -import android.os.RemoteCallback; import android.os.UserHandle; import androidx.test.InstrumentationRegistry; @@ -30,6 +30,8 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.List; + /** * Unit test for {@link AmbientContextManagerService}. * atest FrameworksServicesTests:AmbientContextManagerServiceTest @@ -48,12 +50,22 @@ public class AmbientContextManagerServiceTest { PendingIntent pendingIntent = PendingIntent.getBroadcast( InstrumentationRegistry.getTargetContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); + IAmbientContextObserver observer = new IAmbientContextObserver.Stub() { + @Override + public void onEvents(List<AmbientContextEvent> events) { + } + + @Override + public void onRegistrationComplete(int statusCode) { + } + }; AmbientContextManagerService.ClientRequest clientRequest = new AmbientContextManagerService.ClientRequest(USER_ID, request, - pendingIntent, new RemoteCallback(result -> {})); + pendingIntent.getCreatorPackage(), observer); assertThat(clientRequest.getRequest()).isEqualTo(request); assertThat(clientRequest.getPackageName()).isEqualTo(SYSTEM_PACKAGE_NAME); + assertThat(clientRequest.getObserver()).isEqualTo(observer); assertThat(clientRequest.hasUserId(USER_ID)).isTrue(); assertThat(clientRequest.hasUserId(-1)).isFalse(); assertThat(clientRequest.hasUserIdAndPackageName(USER_ID, SYSTEM_PACKAGE_NAME)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index e6acc904d811..dd7aeb72f7d2 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -40,8 +40,6 @@ import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.ISessionListener; import com.android.internal.statusbar.IStatusBarService; -import com.google.common.collect.ImmutableList; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -89,33 +87,64 @@ public class BiometricContextProviderTest { @Test public void testIsAod() throws RemoteException { - mListener.onDozeChanged(true); + mListener.onDozeChanged(true /* isDozing */, false /* isAwake */); assertThat(mProvider.isAod()).isTrue(); - mListener.onDozeChanged(false); + mListener.onDozeChanged(false /* isDozing */, false /* isAwake */); assertThat(mProvider.isAod()).isFalse(); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false); - mListener.onDozeChanged(true); + mListener.onDozeChanged(true /* isDozing */, false /* isAwake */); assertThat(mProvider.isAod()).isFalse(); - mListener.onDozeChanged(false); + mListener.onDozeChanged(false /* isDozing */, false /* isAwake */); assertThat(mProvider.isAod()).isFalse(); } @Test + public void testIsAwake() throws RemoteException { + mListener.onDozeChanged(false /* isDozing */, true /* isAwake */); + assertThat(mProvider.isAwake()).isTrue(); + mListener.onDozeChanged(false /* isDozing */, false /* isAwake */); + assertThat(mProvider.isAwake()).isFalse(); + mListener.onDozeChanged(true /* isDozing */, true /* isAwake */); + assertThat(mProvider.isAwake()).isTrue(); + mListener.onDozeChanged(true /* isDozing */, false /* isAwake */); + assertThat(mProvider.isAwake()).isFalse(); + } + + @Test public void testSubscribesToAod() throws RemoteException { - final List<Boolean> expected = ImmutableList.of(true, false, true, true, false); final List<Boolean> actual = new ArrayList<>(); mProvider.subscribe(mOpContext, ctx -> { assertThat(ctx).isSameInstanceAs(mOpContext); + assertThat(mProvider.isAod()).isEqualTo(ctx.isAod); + assertThat(mProvider.isAwake()).isFalse(); actual.add(ctx.isAod); }); - for (boolean v : expected) { - mListener.onDozeChanged(v); + for (boolean v : List.of(true, false, true, true, false, false)) { + mListener.onDozeChanged(v /* isDozing */, false /* isAwake */); + } + + assertThat(actual).containsExactly(true, false, true, false).inOrder(); + } + + @Test + public void testSubscribesToAwake() throws RemoteException { + final List<Boolean> actual = new ArrayList<>(); + + mProvider.subscribe(mOpContext, ctx -> { + assertThat(ctx).isSameInstanceAs(mOpContext); + assertThat(ctx.isAod).isFalse(); + assertThat(mProvider.isAod()).isFalse(); + actual.add(mProvider.isAwake()); + }); + + for (boolean v : List.of(true, false, true, true, false, false)) { + mListener.onDozeChanged(false /* isDozing */, v /* isAwake */); } - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); + assertThat(actual).containsExactly(true, false, true, false).inOrder(); } @Test @@ -124,13 +153,13 @@ public class BiometricContextProviderTest { mProvider.subscribe(mOpContext, emptyConsumer); mProvider.unsubscribe(mOpContext); - mListener.onDozeChanged(true); + mListener.onDozeChanged(true /* isDozing */, false /* isAwake */); final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); mProvider.subscribe(mOpContext, nonEmptyConsumer); - mListener.onDozeChanged(false); + mListener.onDozeChanged(false /* isDozing */, false /* isAwake */); mProvider.unsubscribe(mOpContext); - mListener.onDozeChanged(true); + mListener.onDozeChanged(true /* isDozing */, false /* isAwake */); verify(emptyConsumer, never()).accept(any()); verify(nonEmptyConsumer).accept(same(mOpContext)); @@ -171,7 +200,7 @@ public class BiometricContextProviderTest { @Test public void testUpdate() throws RemoteException { - mListener.onDozeChanged(false); + mListener.onDozeChanged(false /* isDozing */, false /* isAwake */); OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */); // default state when nothing has been set @@ -186,7 +215,7 @@ public class BiometricContextProviderTest { final int id = 40 + type; final boolean aod = (type & 1) == 0; - mListener.onDozeChanged(aod); + mListener.onDozeChanged(aod /* isDozing */, false /* isAwake */); mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id)); context = mProvider.updateContext(mOpContext, false /* crypto */); assertThat(context).isSameInstanceAs(mOpContext); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 1ed52fc6a40a..dea4d4fb7c64 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -26,8 +26,8 @@ import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.same; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,6 +96,7 @@ public class FingerprintAuthenticationClientTest { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock private ISession mHal; @Mock @@ -213,21 +214,41 @@ public class FingerprintAuthenticationClientTest { } @Test - public void luxProbeWhenFingerDown() throws RemoteException { + public void luxProbeWhenAwake() throws RemoteException { + when(mBiometricContext.isAwake()).thenReturn(false, true, false); + when(mBiometricContext.isAod()).thenReturn(false); final FingerprintAuthenticationClient client = createClient(); client.start(mCallback); - client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); - verify(mLuxProbe).enable(); + verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture()); + OperationContext opContext = mOperationContextCaptor.getValue(); + verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture()); - client.onAcquired(2, 0); + mContextInjector.getValue().accept(opContext); + verify(mLuxProbe, never()).enable(); + + reset(mLuxProbe); + mContextInjector.getValue().accept(opContext); + verify(mLuxProbe).enable(); verify(mLuxProbe, never()).disable(); - client.onPointerUp(); + mContextInjector.getValue().accept(opContext); verify(mLuxProbe).disable(); + } - client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); - verify(mLuxProbe, times(2)).enable(); + @Test + public void luxProbeDisabledOnAod() throws RemoteException { + when(mBiometricContext.isAwake()).thenReturn(false); + when(mBiometricContext.isAod()).thenReturn(true); + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + + verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture()); + OperationContext opContext = mOperationContextCaptor.getValue(); + verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture()); + + mContextInjector.getValue().accept(opContext); + verify(mLuxProbe, never()).enable(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 97fe9ea41e3d..92e1f27ab624 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -64,6 +64,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; import java.util.function.Consumer; @Presubmit @@ -196,21 +197,22 @@ public class FingerprintEnrollClientTest { } @Test - public void luxProbeWhenFingerDown() throws RemoteException { + public void luxProbeWhenStarted() throws RemoteException { final FingerprintEnrollClient client = createClient(); client.start(mCallback); - client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); verify(mLuxProbe).enable(); client.onAcquired(2, 0); + client.onPointerUp(); + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); verify(mLuxProbe, never()).disable(); + verify(mLuxProbe, never()).destroy(); - client.onPointerUp(); - verify(mLuxProbe).disable(); + client.onEnrollResult(new Fingerprint("f", 30 /* fingerId */, 14 /* deviceId */), + 0 /* remaining */); - client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); - verify(mLuxProbe, times(2)).enable(); + verify(mLuxProbe).destroy(); } @Test 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 a7ca08385b9f..eda91330547e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -6724,55 +6724,51 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test - public void testEnforceCallerCanRequestDeviceIdAttestation_deviceOwnerCaller() + public void testHasDeviceIdAccessUnchecked_deviceOwnerCaller() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); configureContextForAccess(mContext, false); // Device owner should be allowed to request Device ID attestation. - dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1)); + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.CALLER_SYSTEM_USER_UID)).isTrue(); - mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; // Another package must not be allowed to request Device ID attestation. - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(null, admin2.getPackageName()))); - - // Another component that is not the admin must not be allowed to request Device ID - // attestation. - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(admin2))); + assertThat(dpms.hasDeviceIdAccessUnchecked( + DpmMockContext.ANOTHER_PACKAGE_NAME, + DpmMockContext.CALLER_SYSTEM_USER_UID)).isFalse(); } @Test - public void testEnforceCallerCanRequestDeviceIdAttestation_profileOwnerCaller() + public void testHasDeviceIdAccessUnchecked_profileOwnerCaller() throws Exception { configureContextForAccess(mContext, false); // Make sure a security exception is thrown if the device has no profile owner. - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(admin1))); + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.CALLER_UID)).isFalse(); setupProfileOwner(); configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); // The profile owner is allowed to request Device ID attestation. - mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID; - dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1)); + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.CALLER_UID)).isTrue(); // But not another package. - mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(null, admin2.getPackageName()))); + assertThat(dpms.hasDeviceIdAccessUnchecked( + DpmMockContext.ANOTHER_PACKAGE_NAME, + DpmMockContext.CALLER_UID)).isFalse(); // Or another component which is not the admin. - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(admin2, admin2.getPackageName()))); + mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.ANOTHER_UID)).isFalse(); } public void runAsDelegatedCertInstaller(DpmRunnable action) throws Exception { @@ -6788,7 +6784,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test - public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCaller() throws Exception { + public void testHasDeviceIdAccessUnchecked_delegateCaller() throws Exception { setupProfileOwner(); markDelegatedCertInstallerAsInstalled(); @@ -6800,15 +6796,19 @@ public class DevicePolicyManagerTest extends DpmTestBase { configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); // Make sure that the profile owner can still request Device ID attestation. - mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID; - dpms.enforceCallerCanRequestDeviceIdAttestation(dpms.getCallerIdentity(admin1)); + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.CALLER_UID)).isTrue(); - runAsDelegatedCertInstaller(dpm -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(null, DpmMockContext.DELEGATE_PACKAGE_NAME))); + runAsDelegatedCertInstaller(dpm -> assertThat( + dpms.hasDeviceIdAccessUnchecked( + DpmMockContext.DELEGATE_PACKAGE_NAME, + UserHandle.getUid(CALLER_USER_HANDLE, + DpmMockContext.DELEGATE_CERT_INSTALLER_UID))).isTrue()); } @Test - public void testEnforceCallerCanRequestDeviceIdAttestation_delegateCallerWithoutPermissions() + public void testHasDeviceIdAccessUnchecked_delegateCallerWithoutPermissions() throws Exception { setupProfileOwner(); markDelegatedCertInstallerAsInstalled(); @@ -6818,14 +6818,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm -> dpm.setDelegatedScopes(admin1, DpmMockContext.DELEGATE_PACKAGE_NAME, Arrays.asList(DELEGATION_CERT_INSTALL))); - assertExpectException(SecurityException.class, null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(admin1))); + assertThat(dpms.hasDeviceIdAccessUnchecked( + admin1.getPackageName(), + DpmMockContext.CALLER_UID)).isFalse(); runAsDelegatedCertInstaller(dpm -> { - assertExpectException(SecurityException.class, /* messageRegex= */ null, - () -> dpms.enforceCallerCanRequestDeviceIdAttestation( - dpms.getCallerIdentity(null, DpmMockContext.DELEGATE_PACKAGE_NAME))); + assertThat(dpms.hasDeviceIdAccessUnchecked( + DpmMockContext.DELEGATE_PACKAGE_NAME, + DpmMockContext.CALLER_UID)).isFalse(); }); } diff --git a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java index f33001774263..6a27f391ac5b 100644 --- a/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/logcat/LogcatManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.logcat; +import static android.os.Process.INVALID_UID; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -28,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.ContextWrapper; +import android.content.pm.PackageManager; import android.os.ILogd; import android.os.Looper; import android.os.UserHandle; @@ -69,6 +72,8 @@ public class LogcatManagerServiceTest { @Mock private ActivityManagerInternal mActivityManagerInternalMock; @Mock + private PackageManager mPackageManagerMock; + @Mock private ILogd mLogdMock; private LogcatManagerService mService; @@ -81,10 +86,17 @@ public class LogcatManagerServiceTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); + when(mActivityManagerInternalMock.getInstrumentationSourceUid(anyInt())) + .thenReturn(INVALID_UID); + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); - + when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock); + when(mPackageManagerMock.getPackagesForUid(APP1_UID)).thenReturn( + new String[]{APP1_PACKAGE_NAME}); + when(mPackageManagerMock.getPackagesForUid(APP2_UID)).thenReturn( + new String[]{APP2_PACKAGE_NAME}); when(mActivityManagerInternalMock.getPackageNameByPid(APP1_PID)).thenReturn( APP1_PACKAGE_NAME); when(mActivityManagerInternalMock.getPackageNameByPid(APP2_PID)).thenReturn( @@ -136,6 +148,20 @@ public class LogcatManagerServiceTest { } @Test + public void test_RequestFromBackground_ApprovedIfInstrumented() throws Exception { + when(mActivityManagerInternalMock.getInstrumentationSourceUid(APP1_UID)) + .thenReturn(APP1_UID); + when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( + ActivityManager.PROCESS_STATE_RECEIVER); + mService.getBinderService().startThread(APP1_UID, APP1_GID, APP1_PID, FD1); + mTestLooper.dispatchAll(); + + verify(mLogdMock).approve(APP1_UID, APP1_GID, APP1_PID, FD1); + verify(mLogdMock, never()).decline(APP1_UID, APP1_GID, APP1_PID, FD1); + verify(mContextSpy, never()).startActivityAsUser(any(), any()); + } + + @Test public void test_RequestFromForegroundService_DeclinedWithoutPrompt() throws Exception { when(mActivityManagerInternalMock.getUidProcessState(APP1_UID)).thenReturn( ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 0f2fe4434bfa..821ce5edad27 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -75,6 +75,7 @@ import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOO import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID; import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING; import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons; +import static com.android.server.net.NetworkPolicyManagerService.normalizeTemplate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -2030,6 +2031,18 @@ public class NetworkPolicyManagerServiceTest { METERED_NO, actualPolicy.template.getMeteredness()); } + @Test + public void testNormalizeTemplate_duplicatedMergedImsiList() { + final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER) + .setSubscriberIds(Set.of(TEST_IMSI)).build(); + final String[] mergedImsiGroup = new String[] {TEST_IMSI, TEST_IMSI}; + final ArrayList<String[]> mergedList = new ArrayList<>(); + mergedList.add(mergedImsiGroup); + // Verify the duplicated items in the merged IMSI list won't crash the system. + final NetworkTemplate result = normalizeTemplate(template, mergedList); + assertEquals(template, result); + } + private String formatBlockedStateError(int uid, int rule, boolean metered, boolean backgroundRestricted) { return String.format( diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index f1f423d537dc..f6852d8117f1 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -992,6 +992,40 @@ public class PowerManagerServiceTest { } @Test + public void testInattentiveSleep_warningStaysWhenDreaming() { + setMinimumScreenOffTimeoutConfig(5); + setAttentiveWarningDuration(70); + setAttentiveTimeout(100); + createService(); + startSystem(); + advanceTime(50); + verify(mInattentiveSleepWarningControllerMock, atLeastOnce()).show(); + when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(true); + + forceDream(); + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + + advanceTime(10); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); + verify(mInattentiveSleepWarningControllerMock, never()).dismiss(anyBoolean()); + } + + @Test + public void testInattentiveSleep_warningNotShownWhenSleeping() { + setMinimumScreenOffTimeoutConfig(5); + setAttentiveWarningDuration(70); + setAttentiveTimeout(100); + createService(); + startSystem(); + + advanceTime(10); + forceSleep(); + + advanceTime(50); + verify(mInattentiveSleepWarningControllerMock, never()).show(); + } + + @Test public void testInattentiveSleep_noWarningShownIfInattentiveSleepDisabled() { setAttentiveTimeout(-1); createService(); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 5c9348525861..047fcd6ed108 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -16,11 +16,17 @@ package com.android.server.power.stats; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; import android.content.Context; +import android.os.BatteryManager; +import android.os.BatteryStats; +import android.os.BatteryStats.MeasuredEnergyDetails; import android.os.Parcel; import android.util.Log; @@ -28,15 +34,19 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BatteryStatsHistory; +import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.Clock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -51,6 +61,9 @@ public class BatteryStatsHistoryTest { private File mSystemDir; private File mHistoryDir; private final Clock mClock = new MockClock(); + private BatteryStatsHistory mHistory; + @Mock + private BatteryStatsHistory.HistoryStepDetailsCalculator mStepDetailsCalculator; @Before public void setUp() { @@ -65,55 +78,56 @@ public class BatteryStatsHistoryTest { } } mHistoryDir.delete(); + mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, + mStepDetailsCalculator, mClock); + + when(mStepDetailsCalculator.getHistoryStepDetails()) + .thenReturn(new BatteryStats.HistoryStepDetails()); } @Test public void testConstruct() { - BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock); - createActiveFile(history); - verifyFileNumbers(history, Arrays.asList(0)); - verifyActiveFile(history, "0.bin"); + createActiveFile(mHistory); + verifyFileNumbers(mHistory, Arrays.asList(0)); + verifyActiveFile(mHistory, "0.bin"); } @Test public void testStartNextFile() { - BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, - null, mClock); List<Integer> fileList = new ArrayList<>(); fileList.add(0); - createActiveFile(history); + createActiveFile(mHistory); // create file 1 to 31. for (int i = 1; i < 32; i++) { fileList.add(i); - history.startNextFile(); - createActiveFile(history); - verifyFileNumbers(history, fileList); - verifyActiveFile(history, i + ".bin"); + mHistory.startNextFile(); + createActiveFile(mHistory); + verifyFileNumbers(mHistory, fileList); + verifyActiveFile(mHistory, i + ".bin"); } // create file 32 - history.startNextFile(); - createActiveFile(history); + mHistory.startNextFile(); + createActiveFile(mHistory); fileList.add(32); fileList.remove(0); // verify file 0 is deleted. verifyFileDeleted("0.bin"); - verifyFileNumbers(history, fileList); - verifyActiveFile(history, "32.bin"); + verifyFileNumbers(mHistory, fileList); + verifyActiveFile(mHistory, "32.bin"); // create file 33 - history.startNextFile(); - createActiveFile(history); + mHistory.startNextFile(); + createActiveFile(mHistory); // verify file 1 is deleted fileList.add(33); fileList.remove(0); verifyFileDeleted("1.bin"); - verifyFileNumbers(history, fileList); - verifyActiveFile(history, "33.bin"); + verifyFileNumbers(mHistory, fileList); + verifyActiveFile(mHistory, "33.bin"); - assertEquals(0, history.getHistoryUsedSize()); + assertEquals(0, mHistory.getHistoryUsedSize()); // create a new BatteryStatsHistory object, it will pick up existing history files. BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, @@ -168,4 +182,74 @@ public class BatteryStatsHistoryTest { Log.e(TAG, "Error creating history file " + file.getPath(), e); } } + + @Test + public void testRecordMeasuredEnergyDetails() { + mHistory.forceRecordAllHistory(); + mHistory.startRecordingHistory(0, 0, /* reset */ true); + mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80, + 1234); + + MeasuredEnergyDetails details = new MeasuredEnergyDetails(); + MeasuredEnergyDetails.EnergyConsumer consumer1 = + new MeasuredEnergyDetails.EnergyConsumer(); + consumer1.type = 42; + consumer1.ordinal = 0; + consumer1.name = "A"; + + MeasuredEnergyDetails.EnergyConsumer consumer2 = + new MeasuredEnergyDetails.EnergyConsumer(); + consumer2.type = 777; + consumer2.ordinal = 0; + consumer2.name = "B/0"; + + MeasuredEnergyDetails.EnergyConsumer consumer3 = + new MeasuredEnergyDetails.EnergyConsumer(); + consumer3.type = 777; + consumer3.ordinal = 1; + consumer3.name = "B/1"; + + MeasuredEnergyDetails.EnergyConsumer consumer4 = + new MeasuredEnergyDetails.EnergyConsumer(); + consumer4.type = 314; + consumer4.ordinal = 1; + consumer4.name = "C"; + + details.consumers = + new MeasuredEnergyDetails.EnergyConsumer[]{consumer1, consumer2, consumer3, + consumer4}; + details.chargeUC = new long[details.consumers.length]; + for (int i = 0; i < details.chargeUC.length; i++) { + details.chargeUC[i] = 100L * i; + } + details.chargeUC[3] = BatteryStats.POWER_DATA_UNAVAILABLE; + + mHistory.recordMeasuredEnergyDetails(200, 200, details); + + BatteryStatsHistoryIterator iterator = mHistory.iterate(); + BatteryStats.HistoryItem item = new BatteryStats.HistoryItem(); + assertThat(iterator.next(item)).isTrue(); // First item contains current time only + + assertThat(iterator.next(item)).isTrue(); + + String dump = toString(item, /* checkin */ false); + assertThat(dump).contains("+200ms"); + assertThat(dump).contains("ext=E"); + assertThat(dump).contains("Energy: A=0 B/0=100 B/1=200"); + assertThat(dump).doesNotContain("C="); + + String checkin = toString(item, /* checkin */ true); + assertThat(checkin).contains("XE"); + assertThat(checkin).contains("A=0,B/0=100,B/1=200"); + assertThat(checkin).doesNotContain("C="); + } + + private String toString(BatteryStats.HistoryItem item, boolean checkin) { + BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter(); + StringWriter writer = new StringWriter(); + PrintWriter pw = new PrintWriter(writer); + printer.printNextItem(pw, item, 0, checkin, /* verbose */ true); + pw.flush(); + return writer.toString(); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java index 8a0f9247dffb..122f7eb86ac8 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java @@ -26,6 +26,7 @@ import android.hardware.power.stats.EnergyConsumer; import android.hardware.power.stats.EnergyConsumerAttribution; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryStats; import android.util.SparseArray; import android.util.SparseLongArray; @@ -236,6 +237,17 @@ public final class MeasuredEnergySnapshotTest { assertEquals(0, snapshot.getOtherOrdinalNames().length); } + @Test + public void getMeasuredEnergyDetails() { + final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP); + snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0); + MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1); + BatteryStats.MeasuredEnergyDetails details = snapshot.getMeasuredEnergyDetails(delta); + assertThat(details.consumers).hasLength(4); + assertThat(details.chargeUC).isEqualTo(new long[]{2667, 3200000, 0, 0}); + assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0"); + } + private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) { final EnergyConsumer ec = new EnergyConsumer(); ec.id = id; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 8ba9af0d85d0..49879efe4b51 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -15,6 +15,11 @@ */ package com.android.server.notification; +import static android.content.Context.DEVICE_POLICY_SERVICE; +import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; +import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; + import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT; import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE; @@ -34,6 +39,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -94,6 +101,7 @@ public class ManagedServicesTest extends UiServiceTestCase { private UserManager mUm; @Mock private ManagedServices.UserProfiles mUserProfiles; + @Mock private DevicePolicyManager mDpm; Object mLock = new Object(); UserInfo mZero = new UserInfo(0, "zero", 0); @@ -122,6 +130,7 @@ public class ManagedServicesTest extends UiServiceTestCase { getContext().setMockPackageManager(mPm); getContext().addMockSystemService(Context.USER_SERVICE, mUm); + getContext().addMockSystemService(DEVICE_POLICY_SERVICE, mDpm); List<UserInfo> users = new ArrayList<>(); users.add(mZero); @@ -1694,6 +1703,58 @@ public class ManagedServicesTest extends UiServiceTestCase { assertEquals(new ArrayMap(), mService.mIsUserChanged); } + @Test + public void testInfoIsPermittedForProfile_notAllowed() { + when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(false); + + IInterface service = mock(IInterface.class); + when(service.asBinder()).thenReturn(mock(IBinder.class)); + ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); + services.registerSystemService(service, null, 10, 1000); + ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); + + assertFalse(info.isPermittedForProfile(0)); + } + + @Test + public void testInfoIsPermittedForProfile_allows() { + when(mUserProfiles.canProfileUseBoundServices(anyInt())).thenReturn(true); + when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true); + + IInterface service = mock(IInterface.class); + when(service.asBinder()).thenReturn(mock(IBinder.class)); + ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles, + mIpm, APPROVAL_BY_PACKAGE); + services.registerSystemService(service, null, 10, 1000); + ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service); + info.component = new ComponentName("a","b"); + + assertTrue(info.isPermittedForProfile(0)); + } + + @Test + public void testUserProfiles_canProfileUseBoundServices_managedProfile() { + List<UserInfo> users = new ArrayList<>(); + UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0); + profile.userType = USER_TYPE_FULL_SECONDARY; + users.add(profile); + UserInfo managed = new UserInfo(12, "12", 0); + managed.userType = USER_TYPE_PROFILE_MANAGED; + users.add(managed); + UserInfo clone = new UserInfo(13, "13", 0); + clone.userType = USER_TYPE_PROFILE_CLONE; + users.add(clone); + when(mUm.getProfiles(ActivityManager.getCurrentUser())).thenReturn(users); + + ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles(); + profiles.updateCache(mContext); + + assertTrue(profiles.canProfileUseBoundServices(ActivityManager.getCurrentUser())); + assertFalse(profiles.canProfileUseBoundServices(12)); + assertFalse(profiles.canProfileUseBoundServices(13)); + } + private void resetComponentsAndPackages() { ArrayMap<Integer, ArrayMap<Integer, String>> empty = new ArrayMap(1); ArrayMap<Integer, String> emptyPkgs = new ArrayMap(0); 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 ec48e239d272..894a9014c925 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -58,6 +58,9 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.UserHandle.USER_SYSTEM; +import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; +import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; +import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -212,6 +215,7 @@ import com.android.server.lights.LogicalLight; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; import com.android.server.pm.PackageManagerService; +import com.android.server.pm.UserManagerInternal; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; @@ -353,6 +357,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock UserManager mUm; @Mock + UserManagerInternal mUmInternal; + @Mock NotificationHistoryManager mHistoryManager; @Mock StatsManager mStatsManager; @@ -394,6 +400,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUmInternal); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); LocalServices.removeServiceForTest(WindowManagerInternal.class); @@ -4464,6 +4472,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testReadPolicyXml_doesNotRestoreManagedServicesForCloneUser() throws Exception { + final String policyXml = "<notification-policy version=\"1\">" + + "<ranking></ranking>" + + "<enabled_listeners>" + + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + + "</enabled_listeners>" + + "<enabled_assistants>" + + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + + "</enabled_assistants>" + + "<dnd_apps>" + + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + + "</dnd_apps>" + + "</notification-policy>"; + UserInfo ui = new UserInfo(); + ui.id = 10; + ui.userType = USER_TYPE_PROFILE_CLONE; + when(mUmInternal.getUserInfo(10)).thenReturn(ui); + mService.readPolicyXml( + new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())), + true, + 10); + verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10)); + verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10)); + verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10)); + } + + @Test public void testReadPolicyXml_doesNotRestoreManagedServicesForManagedUser() throws Exception { final String policyXml = "<notification-policy version=\"1\">" + "<ranking></ranking>" @@ -4477,7 +4512,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + "</dnd_apps>" + "</notification-policy>"; - when(mUm.isManagedProfile(10)).thenReturn(true); + UserInfo ui = new UserInfo(); + ui.id = 10; + ui.userType = USER_TYPE_PROFILE_MANAGED; + when(mUmInternal.getUserInfo(10)).thenReturn(ui); mService.readPolicyXml( new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())), true, @@ -4501,7 +4539,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + "</dnd_apps>" + "</notification-policy>"; - when(mUm.isManagedProfile(10)).thenReturn(false); + UserInfo ui = new UserInfo(); + ui.id = 10; + ui.userType = USER_TYPE_FULL_SECONDARY; + when(mUmInternal.getUserInfo(10)).thenReturn(ui); mService.readPolicyXml( new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())), true, diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java new file mode 100644 index 000000000000..62875e5863b3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.policy; + +import static android.view.KeyEvent.KEYCODE_POWER; +import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN; +import static android.view.KeyEvent.KEYCODE_VOLUME_UP; + +import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS; +import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; + +import android.view.ViewConfiguration; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for combination key shortcuts. + * + * Build/Install/Run: + * atest WmTests:CombinationKeyTests + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class CombinationKeyTests extends ShortcutKeyTestBase { + private static final long A11Y_KEY_HOLD_MILLIS = 3500; + + /** + * Power-VolDown to take screenshot. + */ + @Test + public void testPowerVolumeDown() { + sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_DOWN}, + ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout()); + mPhoneWindowManager.assertTakeScreenshotCalled(); + } + + /** + * Power-VolUp to show global actions or mute audio. (Phone default behavior) + */ + @Test + public void testPowerVolumeUp() { + // Show global actions. + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS); + sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 0); + mPhoneWindowManager.assertShowGlobalActionsCalled(); + + // Mute audio (hold over 100ms). + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); + sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 100); + mPhoneWindowManager.assertVolumeMute(); + } + + /** + * VolDown-VolUp and hold 3 secs to enable accessibility service. + */ + @Test + public void testVolumeDownVolumeUp() { + sendKeyCombination(new int[]{KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP}, A11Y_KEY_HOLD_MILLIS); + mPhoneWindowManager.assertAccessibilityKeychordCalled(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java index cf571815b4cb..4c698571db16 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java @@ -43,11 +43,11 @@ import java.util.concurrent.TimeUnit; * Test class for {@link KeyCombinationManager}. * * Build/Install/Run: - * atest KeyCombinationTests + * atest KeyCombinationManagerTests */ @SmallTest -public class KeyCombinationTests { +public class KeyCombinationManagerTests { private KeyCombinationManager mKeyCombinationManager; private final CountDownLatch mAction1Triggered = new CountDownLatch(1); @@ -228,4 +228,4 @@ public class KeyCombinationTests { mKeyCombinationManager.interceptKey(firstKeyDown, true); assertTrue(mKeyCombinationManager.getKeyInterceptTimeout(KEYCODE_VOLUME_UP) > eventTime); } -}
\ No newline at end of file +} diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java new file mode 100644 index 000000000000..49af2c1dc681 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.policy; + +import static android.view.KeyEvent.KEYCODE_POWER; +import static android.view.KeyEvent.KEYCODE_VOLUME_UP; + +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS; + +import android.provider.Settings; +import android.view.Display; + +import org.junit.Test; + +/** + * Test class for power key gesture. + * + * Build/Install/Run: + * atest WmTests:PowerKeyGestureTests + */ +public class PowerKeyGestureTests extends ShortcutKeyTestBase { + /** + * Power single press to turn screen on/off. + */ + @Test + public void testPowerSinglePress() { + sendKey(KEYCODE_POWER); + mPhoneWindowManager.assertPowerSleep(); + + // turn screen on when begin from non-interactive. + mPhoneWindowManager.overrideDisplayState(Display.STATE_OFF); + sendKey(KEYCODE_POWER); + mPhoneWindowManager.assertPowerWakeUp(); + mPhoneWindowManager.assertNoPowerSleep(); + } + + /** + * Power double press to trigger camera. + */ + @Test + public void testPowerDoublePress() { + sendKey(KEYCODE_POWER); + sendKey(KEYCODE_POWER); + mPhoneWindowManager.assertCameraLaunch(); + } + + /** + * Power long press to show assistant or global actions. + */ + @Test + public void testPowerLongPress() { + // Show assistant. + mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_ASSISTANT); + sendKey(KEYCODE_POWER, true); + mPhoneWindowManager.assertAssistLaunch(); + + // Show global actions. + mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS); + sendKey(KEYCODE_POWER, true); + mPhoneWindowManager.assertShowGlobalActionsCalled(); + } + + /** + * Ignore power press if combination key already triggered. + */ + @Test + public void testIgnoreSinglePressWhenCombinationKeyTriggered() { + sendKeyCombination(new int[]{KEYCODE_POWER, KEYCODE_VOLUME_UP}, 0); + mPhoneWindowManager.assertNoPowerSleep(); + } + + /** + * When a phone call is active, and INCALL_POWER_BUTTON_BEHAVIOR_HANGUP is enabled, then the + * power button should only stop phone call. The screen should not be turned off (power sleep + * should not be activated). + */ + @Test + public void testIgnoreSinglePressWhenEndCall() { + mPhoneWindowManager.overrideIncallPowerBehavior( + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); + sendKey(KEYCODE_POWER); + mPhoneWindowManager.assertNoPowerSleep(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java new file mode 100644 index 000000000000..6368f4748b26 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.policy; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.KeyEvent.KEYCODE_ALT_LEFT; +import static android.view.KeyEvent.KEYCODE_ALT_RIGHT; +import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; +import static android.view.KeyEvent.KEYCODE_CTRL_RIGHT; +import static android.view.KeyEvent.KEYCODE_META_LEFT; +import static android.view.KeyEvent.KEYCODE_META_RIGHT; +import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT; +import static android.view.KeyEvent.KEYCODE_SHIFT_RIGHT; +import static android.view.KeyEvent.META_ALT_LEFT_ON; +import static android.view.KeyEvent.META_ALT_ON; +import static android.view.KeyEvent.META_ALT_RIGHT_ON; +import static android.view.KeyEvent.META_CTRL_LEFT_ON; +import static android.view.KeyEvent.META_CTRL_ON; +import static android.view.KeyEvent.META_CTRL_RIGHT_ON; +import static android.view.KeyEvent.META_META_LEFT_ON; +import static android.view.KeyEvent.META_META_ON; +import static android.view.KeyEvent.META_META_RIGHT_ON; +import static android.view.KeyEvent.META_SHIFT_LEFT_ON; +import static android.view.KeyEvent.META_SHIFT_ON; +import static android.view.KeyEvent.META_SHIFT_RIGHT_ON; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; + +import static java.util.Collections.unmodifiableMap; + +import android.content.Context; +import android.os.Looper; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import org.junit.After; +import org.junit.Before; + +import java.util.Map; + +class ShortcutKeyTestBase { + TestPhoneWindowManager mPhoneWindowManager; + final Context mContext = getInstrumentation().getTargetContext(); + + /** Modifier key to meta state */ + private static final Map<Integer, Integer> MODIFIER; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON); + map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON); + map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON); + map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON); + map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON); + map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON); + map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON); + map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON); + + MODIFIER = unmodifiableMap(map); + } + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mPhoneWindowManager = new TestPhoneWindowManager(mContext); + } + + @After + public void tearDown() { + mPhoneWindowManager.tearDown(); + } + + void sendKeyCombination(int[] keyCodes, long duration) { + final long downTime = SystemClock.uptimeMillis(); + final int count = keyCodes.length; + final KeyEvent[] events = new KeyEvent[count]; + int metaState = 0; + for (int i = 0; i < count; i++) { + final int keyCode = keyCodes[i]; + final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, + 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, + 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); + event.setDisplayId(DEFAULT_DISPLAY); + events[i] = event; + // The order is important here, metaState could be updated and applied to the next key. + metaState |= MODIFIER.getOrDefault(keyCode, 0); + } + + for (KeyEvent event: events) { + interceptKey(event); + } + + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + for (KeyEvent event: events) { + final long eventTime = SystemClock.uptimeMillis(); + final int keyCode = event.getKeyCode(); + final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, + 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, + InputDevice.SOURCE_KEYBOARD); + interceptKey(upEvent); + metaState &= ~MODIFIER.getOrDefault(keyCode, 0); + } + } + + void sendKey(int keyCode) { + sendKey(keyCode, false); + } + + void sendKey(int keyCode, boolean longPress) { + final long downTime = SystemClock.uptimeMillis(); + final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, + 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, + 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); + event.setDisplayId(DEFAULT_DISPLAY); + interceptKey(event); + + if (longPress) { + final long nextDownTime = downTime + ViewConfiguration.getLongPressTimeout(); + final KeyEvent nextDownevent = new KeyEvent(downTime, nextDownTime, + KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, 0 /*metaState*/, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, + KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD); + interceptKey(nextDownevent); + } + + final long eventTime = longPress + ? SystemClock.uptimeMillis() + ViewConfiguration.getLongPressTimeout() + : SystemClock.uptimeMillis(); + final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, + 0 /*repeat*/, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, + 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); + interceptKey(upEvent); + } + + private void interceptKey(KeyEvent keyEvent) { + int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent); + if ((actions & ACTION_PASS_TO_USER) != 0) { + if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) { + mPhoneWindowManager.dispatchUnhandledKey(keyEvent); + } + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java new file mode 100644 index 000000000000..ee11ac8caf53 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_ON; +import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GO_TO_VOICE_ASSIST; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_NOTHING; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; +import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; + +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.NotificationManager; +import android.app.SearchManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManagerInternal; +import android.media.AudioManagerInternal; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.Vibrator; +import android.service.dreams.DreamManagerInternal; +import android.telecom.TelecomManager; +import android.view.Display; +import android.view.KeyEvent; +import android.view.autofill.AutofillManagerInternal; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.accessibility.AccessibilityShortcutController; +import com.android.server.GestureLauncherService; +import com.android.server.LocalServices; +import com.android.server.vr.VrManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.DisplayPolicy; +import com.android.server.wm.DisplayRotation; +import com.android.server.wm.WindowManagerInternal; + +import org.mockito.Mock; +import org.mockito.MockSettings; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.function.Supplier; + +class TestPhoneWindowManager { + private static final long SHORTCUT_KEY_DELAY_MILLIS = 150; + + private PhoneWindowManager mPhoneWindowManager; + private Context mContext; + + @Mock private WindowManagerInternal mWindowManagerInternal; + @Mock private ActivityManagerInternal mActivityManagerInternal; + @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal; + @Mock private InputManagerInternal mInputManagerInternal; + @Mock private DreamManagerInternal mDreamManagerInternal; + @Mock private PowerManagerInternal mPowerManagerInternal; + @Mock private DisplayManagerInternal mDisplayManagerInternal; + @Mock private AppOpsManager mAppOpsManager; + @Mock private DisplayManager mDisplayManager; + @Mock private PackageManager mPackageManager; + @Mock private TelecomManager mTelecomManager; + @Mock private NotificationManager mNotificationManager; + @Mock private Vibrator mVibrator; + @Mock private PowerManager mPowerManager; + @Mock private WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncsImpl; + @Mock private AudioManagerInternal mAudioManagerInternal; + @Mock private SearchManager mSearchManager; + + @Mock private Display mDisplay; + @Mock private DisplayRotation mDisplayRotation; + @Mock private DisplayPolicy mDisplayPolicy; + @Mock private WindowManagerPolicy.ScreenOnListener mScreenOnListener; + @Mock private GestureLauncherService mGestureLauncherService; + @Mock private GlobalActions mGlobalActions; + @Mock private AccessibilityShortcutController mAccessibilityShortcutController; + + private StaticMockitoSession mMockitoSession; + private HandlerThread mHandlerThread; + private Handler mHandler; + + private class TestInjector extends PhoneWindowManager.Injector { + TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { + super(context, funcs); + } + + AccessibilityShortcutController getAccessibilityShortcutController( + Context context, Handler handler, int initialUserId) { + return mAccessibilityShortcutController; + } + + Supplier<GlobalActions> getGlobalActionsFactory() { + return () -> mGlobalActions; + } + } + + TestPhoneWindowManager(Context context) { + MockitoAnnotations.initMocks(this); + mHandlerThread = new HandlerThread("fake window manager"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mHandler.runWithScissors(()-> setUp(context), 0 /* timeout */); + } + + private void setUp(Context context) { + mPhoneWindowManager = spy(new PhoneWindowManager()); + mContext = spy(context); + + // Use stubOnly() to reduce memory usage if it doesn't need verification. + final MockSettings spyStubOnly = withSettings().stubOnly() + .defaultAnswer(CALLS_REAL_METHODS); + // Return mocked services: LocalServices.getService + mMockitoSession = mockitoSession() + .mockStatic(LocalServices.class, spyStubOnly) + .startMocking(); + + doReturn(mWindowManagerInternal).when( + () -> LocalServices.getService(eq(WindowManagerInternal.class))); + doReturn(mActivityManagerInternal).when( + () -> LocalServices.getService(eq(ActivityManagerInternal.class))); + doReturn(mActivityTaskManagerInternal).when( + () -> LocalServices.getService(eq(ActivityTaskManagerInternal.class))); + doReturn(mInputManagerInternal).when( + () -> LocalServices.getService(eq(InputManagerInternal.class))); + doReturn(mDreamManagerInternal).when( + () -> LocalServices.getService(eq(DreamManagerInternal.class))); + doReturn(mPowerManagerInternal).when( + () -> LocalServices.getService(eq(PowerManagerInternal.class))); + doReturn(mDisplayManagerInternal).when( + () -> LocalServices.getService(eq(DisplayManagerInternal.class))); + doReturn(mGestureLauncherService).when( + () -> LocalServices.getService(eq(GestureLauncherService.class))); + doReturn(null).when(() -> LocalServices.getService(eq(VrManagerInternal.class))); + doReturn(null).when(() -> LocalServices.getService(eq(AutofillManagerInternal.class))); + + doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class)); + doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(false).when(mPackageManager).hasSystemFeature(any()); + try { + doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager) + .getActivityInfo(any(), anyInt()); + doReturn(new String[] { "testPackage" }).when(mPackageManager) + .canonicalToCurrentPackageNames(any()); + } catch (PackageManager.NameNotFoundException ignored) { } + + doReturn(mTelecomManager).when(mContext).getSystemService(eq(Context.TELECOM_SERVICE)); + doReturn(mNotificationManager).when(mContext) + .getSystemService(eq(NotificationManager.class)); + doReturn(mVibrator).when(mContext).getSystemService(eq(Context.VIBRATOR_SERVICE)); + + final PowerManager.WakeLock wakeLock = mock(PowerManager.WakeLock.class); + doReturn(wakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); + doReturn(mPowerManager).when(mContext).getSystemService(eq(Context.POWER_SERVICE)); + doReturn(true).when(mPowerManager).isInteractive(); + + doReturn(mDisplay).when(mDisplayManager).getDisplay(eq(DEFAULT_DISPLAY)); + doReturn(STATE_ON).when(mDisplay).getState(); + doReturn(true).when(mDisplayPolicy).isAwake(); + doNothing().when(mDisplayPolicy).takeScreenshot(anyInt(), anyInt()); + doReturn(mDisplayPolicy).when(mDisplayRotation).getDisplayPolicy(); + doReturn(mScreenOnListener).when(mDisplayPolicy).getScreenOnListener(); + mPhoneWindowManager.setDefaultDisplay(new WindowManagerPolicy.DisplayContentInfo() { + @Override + public DisplayRotation getDisplayRotation() { + return mDisplayRotation; + } + @Override + public Display getDisplay() { + return mDisplay; + } + }); + + doNothing().when(mPhoneWindowManager).initializeHdmiState(); + doNothing().when(mPhoneWindowManager).updateSettings(); + doNothing().when(mPhoneWindowManager).screenTurningOn(anyInt(), any()); + doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt()); + doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt()); + doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt()); + + mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl)); + mPhoneWindowManager.systemReady(); + mPhoneWindowManager.systemBooted(); + + overrideLaunchAccessibility(); + } + + void tearDown() { + mHandlerThread.quitSafely(); + mMockitoSession.finishMocking(); + } + + // Override accessibility setting and perform function. + private void overrideLaunchAccessibility() { + doReturn(true).when(mAccessibilityShortcutController) + .isAccessibilityShortcutAvailable(anyBoolean()); + doNothing().when(mAccessibilityShortcutController).performAccessibilityShortcut(); + } + + int interceptKeyBeforeQueueing(KeyEvent event) { + return mPhoneWindowManager.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE); + } + + long interceptKeyBeforeDispatching(KeyEvent event) { + return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/, + event, FLAG_INTERACTIVE); + } + + void dispatchUnhandledKey(KeyEvent event) { + mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE); + } + + void waitForIdle() { + mHandler.runWithScissors(() -> { }, 0 /* timeout */); + } + + /** + * Below functions will override the setting or the policy behavior. + */ + void overridePowerVolumeUp(int behavior) { + mPhoneWindowManager.mPowerVolUpBehavior = behavior; + + // override mRingerToggleChord as mute so we could trigger the behavior. + if (behavior == POWER_VOLUME_UP_BEHAVIOR_MUTE) { + mPhoneWindowManager.mRingerToggleChord = VOLUME_HUSH_MUTE; + doReturn(mAudioManagerInternal).when( + () -> LocalServices.getService(eq(AudioManagerInternal.class))); + } + } + + // Override assist perform function. + void overrideLongPressOnPower(int behavior) { + mPhoneWindowManager.mLongPressOnPowerBehavior = behavior; + + switch (behavior) { + case LONG_PRESS_POWER_NOTHING: + case LONG_PRESS_POWER_GLOBAL_ACTIONS: + case LONG_PRESS_POWER_SHUT_OFF: + case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: + case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: + break; + case LONG_PRESS_POWER_ASSISTANT: + doNothing().when(mPhoneWindowManager).sendCloseSystemWindows(); + doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + doReturn(mSearchManager).when(mContext) + .getSystemService(eq(Context.SEARCH_SERVICE)); + mPhoneWindowManager.mLongPressOnPowerAssistantTimeoutMs = 500; + break; + } + } + + void overrideDisplayState(int state) { + doReturn(state).when(mDisplay).getState(); + Mockito.reset(mPowerManager); + } + + void overrideIncallPowerBehavior(int behavior) { + mPhoneWindowManager.mIncallPowerBehavior = behavior; + setPhoneCallIsInProgress(); + } + + void setPhoneCallIsInProgress() { + // Let device has an ongoing phone call. + doReturn(false).when(mTelecomManager).isRinging(); + doReturn(true).when(mTelecomManager).isInCall(); + doReturn(true).when(mTelecomManager).endCall(); + } + + /** + * Below functions will check the policy behavior could be invoked. + */ + void assertTakeScreenshotCalled() { + waitForIdle(); + verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS)) + .takeScreenshot(anyInt(), anyInt()); + } + + void assertShowGlobalActionsCalled() { + waitForIdle(); + verify(mPhoneWindowManager).showGlobalActions(); + verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS)) + .showDialog(anyBoolean(), anyBoolean()); + verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)) + .userActivity(anyLong(), anyBoolean()); + } + + void assertVolumeMute() { + waitForIdle(); + verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS)) + .silenceRingerModeInternal(eq("volume_hush")); + } + + void assertAccessibilityKeychordCalled() { + waitForIdle(); + verify(mAccessibilityShortcutController, + timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut(); + } + + void assertPowerSleep() { + waitForIdle(); + verify(mPowerManager, + timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt()); + } + + void assertPowerWakeUp() { + waitForIdle(); + verify(mPowerManager, + timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString()); + } + + void assertNoPowerSleep() { + waitForIdle(); + verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt()); + } + + void assertCameraLaunch() { + waitForIdle(); + // GestureLauncherService should receive interceptPowerKeyDown twice. + verify(mGestureLauncherService, times(2)) + .interceptPowerKeyDown(any(), anyBoolean(), any()); + } + + void assertAssistLaunch() { + waitForIdle(); + verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java index 8b0a54050c3c..58b0e16eefe8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java @@ -36,6 +36,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; import java.util.Set; @@ -88,7 +90,8 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mDefaultDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos( + DEFAULT_DISPLAY); // An entry for rotation 0, for a display that can be in a single state. assertThat(displayInfos.size()).isEqualTo(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); @@ -105,9 +108,10 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mSecondDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); - Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); - Set<DisplayInfo> defaultDisplayInfos = new ArraySet<>(); - Set<DisplayInfo> secondDisplayInfos = new ArraySet<>(); + List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos( + DEFAULT_DISPLAY); + List<DisplayInfo> defaultDisplayInfos = new ArrayList<>(); + List<DisplayInfo> secondDisplayInfos = new ArrayList<>(); for (DisplayInfo di : displayInfos) { if ((di.flags & FLAG_PRESENTATION) != 0) { secondDisplayInfos.add(di); @@ -137,12 +141,13 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { mPossibleDisplayInfo.add(mSecondDisplayInfo); mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId); - Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + List<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos( + DEFAULT_DISPLAY); // An entry for rotation 0, for the default display. assertThat(displayInfos).hasSize(1); assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); - Set<DisplayInfo> secondStateEntries = + List<DisplayInfo> secondStateEntries = mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId); // An entry for rotation 0, for the second display. assertThat(secondStateEntries).hasSize(1); @@ -157,7 +162,7 @@ public class PossibleDisplayInfoMapperTests extends WindowTestsBase { outDisplayInfo.logicalHeight = logicalBounds.height(); } - private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos, + private static void assertPossibleDisplayInfoEntries(List<DisplayInfo> displayInfos, DisplayInfo expectedDisplayInfo) { for (DisplayInfo displayInfo : displayInfos) { assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6d33aaffdf24..17387061a86e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1640,6 +1640,75 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN}) + public void testOverrideMinAspectRatioExcludePortraitFullscreen() { + setUpDisplaySizeWithApp(2600, 1600); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // Non-resizable portrait activity + prepareUnresizable(activity, 0f, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + // At first, OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply, because the + // display is in landscape + assertEquals(1600, activity.getBounds().height()); + assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, + activity.getBounds().width(), 0.5); + + rotateDisplay(activity.mDisplayContent, ROTATION_90); + prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT); + + // Now the display is in portrait fullscreen, so the override is applied making the content + // fullscreen + assertEquals(activity.getBounds(), activity.mDisplayContent.getBounds()); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN}) + public void testOverrideMinAspectRatioExcludePortraitFullscreenNotApplied() { + // In this test, the activity is not in fullscreen, so the override is not applied + setUpDisplaySizeWithApp(2600, 1600); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, activity.getDisplayContent()); + + // Move first activity to split screen which takes half of the screen. + organizer.mPrimary.setBounds(0, 0, 1300, 1600); + organizer.putTaskToPrimary(mTask, true); + + // Non-resizable portrait activity + prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT); + + // OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply here because the + // display is not in fullscreen, so OVERRIDE_MIN_ASPECT_RATIO_LARGE applies instead + assertEquals(1600, activity.getBounds().height()); + assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test public void testSplitAspectRatioForUnresizableLandscapeApps() { // Set up a display in portrait and ignoring orientation request. int screenWidth = 1400; diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index ab7e8eab28c3..9e6c4c58371b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -235,6 +235,7 @@ public class SystemServicesTestRule implements TestRule { doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE)); mStubbedWakeLock = createStubbedWakeLock(false /* needVerification */); doReturn(mStubbedWakeLock).when(pm).newWakeLock(anyInt(), anyString()); + doReturn(mStubbedWakeLock).when(pm).newWakeLock(anyInt(), anyString(), anyInt()); // DisplayManagerInternal final DisplayManagerInternal dmi = mock(DisplayManagerInternal.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 24cdc0fb329f..9bdf750767b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; +import static android.window.TaskFragmentOrganizer.getTransitionType; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; @@ -49,8 +50,8 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -78,7 +79,6 @@ import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentTransaction; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import androidx.test.filters.SmallTest; @@ -136,6 +136,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment = new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */); mTransaction = new WindowContainerTransaction(); + mTransaction.setTaskFragmentOrganizer(mIOrganizer); mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken(); mDefinition = new RemoteAnimationDefinition(); mErrorToken = new Binder(); @@ -155,11 +156,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration(); // To prevent it from calling the real server. - doNothing().when(mOrganizer).onTransactionHandled(any(), any()); + doNothing().when(mOrganizer).applyTransaction(any(), anyInt(), anyBoolean()); + doNothing().when(mOrganizer).onTransactionHandled(any(), any(), anyInt(), anyBoolean()); + + mController.registerOrganizer(mIOrganizer); } @Test public void testCallTaskFragmentCallbackWithoutRegister_throwsException() { + mController.unregisterOrganizer(mIOrganizer); + doReturn(mTask).when(mTaskFragment).getTask(); assertThrows(IllegalArgumentException.class, () -> mController @@ -175,8 +181,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentAppeared() { - mController.registerOrganizer(mIOrganizer); - // No-op when the TaskFragment is not attached. mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -194,7 +198,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentInfoChanged() { - mController.registerOrganizer(mIOrganizer); setupMockParent(mTaskFragment, mTask); // No-op if onTaskFragmentAppeared is not called yet. @@ -233,8 +236,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentVanished() { - mController.registerOrganizer(mIOrganizer); - mTaskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); mController.dispatchPendingEvents(); @@ -244,7 +245,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentVanished_clearUpRemaining() { - mController.registerOrganizer(mIOrganizer); setupMockParent(mTaskFragment, mTask); // Not trigger onTaskFragmentAppeared. @@ -270,7 +270,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTaskFragmentParentInfoChanged() { - mController.registerOrganizer(mIOrganizer); setupMockParent(mTaskFragment, mTask); mTask.getConfiguration().smallestScreenWidthDp = 10; @@ -317,7 +316,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testOnTaskFragmentError() { final Throwable exception = new IllegalArgumentException("Test exception"); - mController.registerOrganizer(mIOrganizer); mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(), mErrorToken, null /* taskFragment */, HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS, exception); @@ -332,7 +330,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Make sure the activity pid/uid is the same as the organizer caller. final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); - mController.registerOrganizer(mIOrganizer); final ActivityRecord activity = createActivityRecord(mDisplayContent); final Task task = activity.getTask(); activity.info.applicationInfo.uid = uid; @@ -375,8 +372,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid, DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); - mController.registerOrganizer(mIOrganizer); - mOrganizer.applyTransaction(mTransaction); final Task task = createTask(mDisplayContent); task.addChild(mTaskFragment, POSITION_TOP); final ActivityRecord activity = createActivityRecord(task); @@ -404,7 +399,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activity.intent, change.getActivityIntent()); assertNotEquals(activity.token, change.getActivityToken()); mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken()); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertEquals(mTaskFragment, activity.getTaskFragment()); // The temporary token can only be used once. @@ -414,7 +409,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testRegisterRemoteAnimations() { - mController.registerOrganizer(mIOrganizer); mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition); assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID)); @@ -425,23 +419,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testWindowContainerTransaction_setTaskFragmentOrganizer() { - mOrganizer.applyTransaction(mTransaction); - - assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); - - mTransaction = new WindowContainerTransaction(); - mOrganizer.applySyncTransaction( - mTransaction, mock(WindowContainerTransactionCallback.class)); - - assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer()); - } - - @Test - public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() - throws RemoteException { - mOrganizer.applyTransaction(mTransaction); - + public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() { // Throw exception if the transaction is trying to change a window that is not organized by // the organizer. mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100)); @@ -457,10 +435,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test - public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() - throws RemoteException { - mController.registerOrganizer(mIOrganizer); - mOrganizer.applyTransaction(mTransaction); + public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment() { doReturn(true).when(mTaskFragment).isAttached(); // Throw exception if the transaction is trying to change a window that is not organized by @@ -486,13 +461,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots() - throws RemoteException { - mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer); + public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots() { final TaskFragment taskFragment2 = new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */); final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken(); - mOrganizer.applyTransaction(mTransaction); // Throw exception if the transaction is trying to change a window that is not organized by // the organizer. @@ -513,9 +485,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() - throws RemoteException { - mController.registerOrganizer(mIOrganizer); + public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() { final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent); final IBinder fragmentToken = new Binder(); @@ -526,11 +496,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class)); mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class), null /* options */); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // Successfully created a TaskFragment. - final TaskFragment taskFragment = mAtm.mWindowOrganizerController - .getTaskFragment(fragmentToken); + final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken); assertNotNull(taskFragment); assertEquals(ownerActivity.getTask(), taskFragment.getTask()); } @@ -539,7 +508,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_enforceTaskFragmentOrganized_startActivityInTaskFragment() { final Task task = createTask(mDisplayContent); final ActivityRecord ownerActivity = createActivityRecord(task); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -562,7 +530,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_enforceTaskFragmentOrganized_reparentActivityInTaskFragment() { final Task task = createTask(mDisplayContent); final ActivityRecord activity = createActivityRecord(task); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -583,7 +550,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testApplyTransaction_enforceTaskFragmentOrganized_setAdjacentTaskFragments() { final Task task = createTask(mDisplayContent); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -623,7 +589,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testApplyTransaction_enforceTaskFragmentOrganized_requestFocusOnTaskFragment() { final Task task = createTask(mDisplayContent); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -642,44 +607,38 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_createTaskFragment_failForDifferentUid() - throws RemoteException { - mController.registerOrganizer(mIOrganizer); + public void testApplyTransaction_createTaskFragment_failForDifferentUid() { final ActivityRecord activity = createActivityRecord(mDisplayContent); final int uid = Binder.getCallingUid(); final IBinder fragmentToken = new Binder(); final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( mOrganizerToken, fragmentToken, activity.token).build(); - mOrganizer.applyTransaction(mTransaction); mTransaction.createTaskFragment(params); // Fail to create TaskFragment when the task uid is different from caller. activity.info.applicationInfo.uid = uid; activity.getTask().effectiveUid = uid + 1; - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); // Fail to create TaskFragment when the task uid is different from owner activity. activity.info.applicationInfo.uid = uid + 1; activity.getTask().effectiveUid = uid; - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); // Successfully created a TaskFragment for same uid. activity.info.applicationInfo.uid = uid; activity.getTask().effectiveUid = uid; - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); } @Test - public void testApplyTransaction_enforceHierarchyChange_reparentChildren() - throws RemoteException { - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); + public void testApplyTransaction_enforceHierarchyChange_reparentChildren() { doReturn(true).when(mTaskFragment).isAttached(); // Throw exception if the transaction is trying to change a window that is not organized by @@ -699,14 +658,12 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() - throws RemoteException { + public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() { final Task task = createTask(mDisplayContent); final ActivityRecord activity = createActivityRecord(task); // Skip manipulate the SurfaceControl. doNothing().when(activity).setDropInputMode(anyInt()); mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -717,15 +674,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities(); } @Test public void testApplyTransaction_requestFocusOnTaskFragment() { - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); final Task task = createTask(mDisplayContent); final IBinder token0 = new Binder(); final TaskFragment tf0 = new TaskFragmentBuilder(mAtm) @@ -750,7 +705,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final ActivityRecord activityInOtherTask = createActivityRecord(mDefaultDisplay); mDisplayContent.setFocusedApp(activityInOtherTask); mTransaction.requestFocusOnTaskFragment(token0); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertEquals(activityInOtherTask, mDisplayContent.mFocusedApp); @@ -758,7 +713,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { activity0.setState(ActivityRecord.State.PAUSED, "test"); activity1.setState(ActivityRecord.State.RESUMED, "test"); mDisplayContent.setFocusedApp(activity1); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertEquals(activity1, mDisplayContent.mFocusedApp); @@ -766,28 +721,29 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // has a resumed activity. activity0.setState(ActivityRecord.State.RESUMED, "test"); mDisplayContent.setFocusedApp(activity1); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertEquals(activity0, mDisplayContent.mFocusedApp); } @Test public void testApplyTransaction_skipTransactionForUnregisterOrganizer() { + mController.unregisterOrganizer(mIOrganizer); final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent); final IBinder fragmentToken = new Binder(); // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken); - mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // Nothing should happen as the organizer is not registered. - assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); mController.registerOrganizer(mIOrganizer); - mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // Successfully created when the organizer is registered. - assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken)); + assertNotNull(mWindowOrganizerController.getTaskFragment(fragmentToken)); } @Test @@ -799,13 +755,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Not allow to start activity in a TaskFragment that is in a PIP Task. mTransaction.startActivityInTaskFragment( - mFragmentToken, activity.token, new Intent(), null /* activityOptions */) + mFragmentToken, activity.token, new Intent(), null /* activityOptions */) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(), any(), any(), anyInt(), anyInt(), any()); - verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), + verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT), any(IllegalArgumentException.class)); @@ -820,7 +776,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Not allow to reparent activity to a TaskFragment that is in a PIP Task. mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), @@ -836,9 +792,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Not allow to set adjacent on a TaskFragment that is in a PIP Task. mTransaction.setAdjacentTaskFragments(mFragmentToken, null /* fragmentToken2 */, - null /* options */) + null /* options */) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), @@ -849,7 +805,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testTaskFragmentInPip_createTaskFragment() { - mController.registerOrganizer(mIOrganizer); final Task pipTask = createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); final ActivityRecord activity = createActivityRecord(pipTask); @@ -859,7 +814,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Not allow to create TaskFragment in a PIP Task. createTaskFragmentFromOrganizer(mTransaction, activity, fragmentToken); mTransaction.setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(null), eq(HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT), @@ -875,7 +830,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Not allow to delete a TaskFragment that is in a PIP Task. mTransaction.deleteTaskFragment(mFragmentWindowToken) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT), @@ -885,7 +840,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Allow organizer to delete empty TaskFragment for cleanup. final Task task = mTaskFragment.getTask(); mTaskFragment.removeChild(mTaskFragment.getTopMostActivity()); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertNull(mWindowOrganizerController.getTaskFragment(mFragmentToken)); assertNull(task.getTopChild()); @@ -916,7 +871,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { doReturn(false).when(task).shouldBeVisible(any()); // Sending events - mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -942,7 +896,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { taskFragment.setResumedActivity(null, "test"); // Sending events - mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -977,7 +930,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertTrue(parentTask.shouldBeVisible(null)); // Dispatch pending info changed event from creating the activity - mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -1013,7 +965,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertTrue(task.shouldBeVisible(null)); // Dispatch pending info changed event from creating the activity - mController.registerOrganizer(mIOrganizer); taskFragment.mTaskFragmentAppearedSent = true; mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment); mController.dispatchPendingEvents(); @@ -1039,13 +990,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { * {@link WindowOrganizerController}. */ @Test - public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment() - throws RemoteException { - mController.registerOrganizer(mIOrganizer); + public void testTaskFragmentRemoved_cleanUpEmbeddedTaskFragment() { final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent); final IBinder fragmentToken = new Binder(); createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken); - mAtm.getWindowOrganizerController().applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken); assertNotNull(taskFragment); @@ -1060,9 +1009,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { * its parent bounds. */ @Test - public void testUntrustedEmbedding_configChange() throws RemoteException { - mController.registerOrganizer(mIOrganizer); - mOrganizer.applyTransaction(mTransaction); + public void testUntrustedEmbedding_configChange() { mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, "Test:TaskFragmentOrganizer" /* processName */); doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode(); @@ -1123,8 +1070,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Make minWidth/minHeight exceeds the TaskFragment bounds. activity.info.windowLayout = new ActivityInfo.WindowLayout( 0, 0, 0, 0, 0, mTaskFragBounds.width() + 10, mTaskFragBounds.height() + 10); - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .setFragmentToken(mFragmentToken) @@ -1137,7 +1082,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // minimum dimensions. mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // The pending event will be dispatched on the handler (from requestTraversal). waitHandlerIdle(mWm.mAnimationHandler); @@ -1148,8 +1093,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testMinDimensionViolation_ReparentChildren() { final Task task = createTask(mDisplayContent); - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); final IBinder oldFragToken = new Binder(); final TaskFragment oldTaskFrag = new TaskFragmentBuilder(mAtm) .setParentTask(task) @@ -1175,7 +1118,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTransaction.reparentChildren(oldTaskFrag.mRemoteToken.toWindowContainerToken(), mTaskFragment.mRemoteToken.toWindowContainerToken()) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); // The pending event will be dispatched on the handler (from requestTraversal). waitHandlerIdle(mWm.mAnimationHandler); @@ -1186,8 +1129,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testMinDimensionViolation_SetBounds() { final Task task = createTask(mDisplayContent); - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(task) .createActivityCount(1) @@ -1206,7 +1147,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // minimum dimensions. mTransaction.setBounds(mTaskFragment.mRemoteToken.toWindowContainerToken(), mTaskFragBounds) .setErrorCallbackToken(mErrorToken); - mWindowOrganizerController.applyTransaction(mTransaction); + assertApplyTransactionAllowed(mTransaction); assertWithMessage("setBounds must not be performed.") .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds()); @@ -1214,18 +1155,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { @Test public void testOnTransactionReady_invokeOnTransactionHandled() { - mController.registerOrganizer(mIOrganizer); final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); mOrganizer.onTransactionReady(transaction); // Organizer should always trigger #onTransactionHandled when receives #onTransactionReady - verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any()); - verify(mOrganizer, never()).applyTransaction(any()); + verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any(), + anyInt(), anyBoolean()); + verify(mOrganizer, never()).applyTransaction(any(), anyInt(), anyBoolean()); } @Test public void testDispatchTransaction_deferTransitionReady() { - mController.registerOrganizer(mIOrganizer); setupMockParent(mTaskFragment, mTask); final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class); final ArgumentCaptor<WindowContainerTransaction> wctCaptor = @@ -1238,12 +1178,15 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Defer transition when send TaskFragment transaction during transition collection. verify(mTransitionController).deferTransitionReady(); - verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture()); + verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture(), + anyInt(), anyBoolean()); - mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue()); + final IBinder transactionToken = tokenCaptor.getValue(); + final WindowContainerTransaction wct = wctCaptor.getValue(); + wct.setTaskFragmentOrganizer(mIOrganizer); + mController.onTransactionHandled(transactionToken, wct, getTransitionType(wct), + false /* shouldApplyIndependently */); - // Apply the organizer change and continue transition. - verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue()); verify(mTransitionController).continueTransitionReady(); } @@ -1258,7 +1201,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { ownerActivity.getTask().effectiveUid = uid; final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( mOrganizerToken, fragmentToken, ownerActivity.token).build(); - mOrganizer.applyTransaction(wct); + wct.setTaskFragmentOrganizer(mIOrganizer); // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment. wct.createTaskFragment(params); @@ -1266,22 +1209,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Asserts that applying the given transaction will throw a {@link SecurityException}. */ private void assertApplyTransactionDisallowed(WindowContainerTransaction t) { - assertThrows(SecurityException.class, () -> { - try { - mAtm.getWindowOrganizerController().applyTransaction(t); - } catch (RemoteException e) { - fail(); - } - }); + assertThrows(SecurityException.class, () -> + mController.applyTransaction(t, getTransitionType(t), + false /* shouldApplyIndependently */)); } /** Asserts that applying the given transaction will not throw any exception. */ private void assertApplyTransactionAllowed(WindowContainerTransaction t) { - try { - mAtm.getWindowOrganizerController().applyTransaction(t); - } catch (RemoteException e) { - fail(); - } + mController.applyTransaction(t, getTransitionType(t), false /* shouldApplyIndependently */); } /** Asserts that there will be a transaction for TaskFragment appeared. */ @@ -1367,8 +1302,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups an embedded TaskFragment in a PIP Task. */ private void setupTaskFragmentInPip() { - mOrganizer.applyTransaction(mTransaction); - mController.registerOrganizer(mIOrganizer); mTaskFragment = new TaskFragmentBuilder(mAtm) .setCreateParentTask() .setFragmentToken(mFragmentToken) @@ -1376,8 +1309,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .createActivityCount(1) .build(); mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken(); - mAtm.mWindowOrganizerController.mLaunchTaskFragments - .put(mFragmentToken, mTaskFragment); + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); mTaskFragment.getTask().setWindowingMode(WINDOWING_MODE_PINNED); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index fba4ff1f1c25..82887135c73d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -33,17 +33,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; @@ -61,9 +65,12 @@ import androidx.test.filters.SmallTest; import com.android.server.wm.utils.WmDisplayCutout; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + /** * Tests for the {@link WallpaperController} class. * @@ -74,6 +81,18 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class WallpaperControllerTests extends WindowTestsBase { + private static final int INITIAL_WIDTH = 600; + private static final int INITIAL_HEIGHT = 900; + private static final int SECOND_WIDTH = 300; + + @Before + public void setup() { + Resources resources = mWm.mContext.getResources(); + spyOn(resources); + doReturn(false).when(resources).getBoolean( + com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); + } + @Test public void testWallpaperScreenshot() { WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); @@ -365,6 +384,108 @@ public class WallpaperControllerTests extends WindowTestsBase { assertTrue(token.isVisible()); } + private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) { + spyOn(dc.mWmService); + DisplayInfo firstDisplay = dc.getDisplayInfo(); + DisplayInfo secondDisplay = new DisplayInfo(firstDisplay); + // Second display is narrower than first display. + secondDisplay.logicalWidth = width; + secondDisplay.logicalHeight = height; + doReturn(List.of(firstDisplay, secondDisplay)).when( + dc.mWmService).getPossibleDisplayInfoLocked(anyInt()); + } + + private static void resizeDisplayAndWallpaper(DisplayContent dc, WindowState wallpaperWindow, + int width, int height) { + dc.setBounds(0, 0, width, height); + dc.updateOrientation(); + dc.sendNewConfiguration(); + spyOn(wallpaperWindow); + doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getLastReportedBounds(); + } + + @Test + public void testUpdateWallpaperOffset_initial_shouldCenterDisabled() { + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH, + INITIAL_HEIGHT).build(); + dc.mWallpaperController.setShouldOffsetWallpaperCenter(false); + prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT); + final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH, + INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Wallpaper centering is disabled, so no offset. + assertThat(wallpaperWindow.mXOffset).isEqualTo(0); + assertThat(wallpaperWindow.mYOffset).isEqualTo(0); + } + + @Test + public void testUpdateWallpaperOffset_initial_shouldCenterEnabled() { + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH, + INITIAL_HEIGHT).build(); + dc.mWallpaperController.setShouldOffsetWallpaperCenter(true); + prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT); + final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH, + INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Wallpaper matches first display, so has no offset. + assertThat(wallpaperWindow.mXOffset).isEqualTo(0); + assertThat(wallpaperWindow.mYOffset).isEqualTo(0); + } + + @Test + public void testUpdateWallpaperOffset_resize_shouldCenterEnabled() { + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH, + INITIAL_HEIGHT).build(); + dc.mWallpaperController.setShouldOffsetWallpaperCenter(true); + prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT); + final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH, + INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Resize display to match second display bounds. + resizeDisplayAndWallpaper(dc, wallpaperWindow, SECOND_WIDTH, INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Wallpaper is 300 wider than second display. + assertThat(wallpaperWindow.mXOffset).isEqualTo(-Math.abs(INITIAL_WIDTH - SECOND_WIDTH) / 2); + assertThat(wallpaperWindow.mYOffset).isEqualTo(0); + } + + @Test + public void testUpdateWallpaperOffset_resize_shouldCenterDisabled() { + final DisplayContent dc = new TestDisplayContent.Builder(mAtm, INITIAL_WIDTH, + INITIAL_HEIGHT).build(); + dc.mWallpaperController.setShouldOffsetWallpaperCenter(false); + prepareSmallerSecondDisplay(dc, SECOND_WIDTH, INITIAL_HEIGHT); + final WindowState wallpaperWindow = createWallpaperWindow(dc, INITIAL_WIDTH, + INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Resize display to match second display bounds. + resizeDisplayAndWallpaper(dc, wallpaperWindow, SECOND_WIDTH, INITIAL_HEIGHT); + + dc.mWallpaperController.updateWallpaperOffset(wallpaperWindow, false); + + // Wallpaper is 300 wider than second display, but offset disabled. + assertThat(wallpaperWindow.mXOffset).isEqualTo(0); + assertThat(wallpaperWindow.mYOffset).isEqualTo(0); + } + + private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) { + final WindowState wallpaperWindow = createWallpaperWindow(dc); + // Wallpaper is cropped to match first display. + wallpaperWindow.getWindowFrames().mParentFrame.set(new Rect(0, 0, width, height)); + wallpaperWindow.getWindowFrames().mFrame.set(0, 0, width, height); + return wallpaperWindow; + } + private WindowState createWallpaperWindow(DisplayContent dc) { final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class), true /* explicit */, dc, true /* ownerCanManageAppTokens */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java new file mode 100644 index 000000000000..d2552718f218 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.atLeast; + +import android.content.Intent; +import android.platform.test.annotations.Presubmit; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link WindowContainerTransaction}. + * + * Build/Install/Run: + * atest WmTests:WindowContainerTransactionTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class WindowContainerTransactionTests extends WindowTestsBase { + + @Test + public void testRemoveTask() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + WindowContainerToken token = task.getTaskInfo().token; + wct.removeTask(token); + applyTransaction(wct); + + // There is still an activity to be destroyed, so the task is not removed immediately. + assertNotNull(task.getParent()); + assertTrue(rootTask.hasChild()); + assertTrue(task.hasChild()); + assertTrue(activity.finishing); + + activity.destroyed("testRemoveContainer"); + // Assert that the container was removed after the activity is destroyed. + assertNull(task.getParent()); + assertEquals(0, task.getChildCount()); + assertNull(activity.getParent()); + verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task); + verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask); + } + + private Task createTask(int taskId) { + return new Task.Builder(mAtm) + .setTaskId(taskId) + .setIntent(new Intent()) + .setRealActivity(ActivityBuilder.getDefaultComponent()) + .setEffectiveUid(10050) + .buildInner(); + } + + private void applyTransaction(@NonNull WindowContainerTransaction t) { + if (!t.isEmpty()) { + mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); + } + } +} diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java index f169926fb3ac..1b34b817def1 100644 --- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java +++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java @@ -772,7 +772,7 @@ public class AppTimeLimitController { observerApp.appUsageGroups.append(observerId, group); if (DEBUG) { - Slog.d(TAG, "addObserver " + observed + " for " + timeLimit); + Slog.d(TAG, "addObserver " + Arrays.toString(observed) + " for " + timeLimit); } user.addUsageGroup(group); @@ -881,7 +881,7 @@ public class AppTimeLimitController { observerApp.appUsageLimitGroups.append(observerId, group); if (DEBUG) { - Slog.d(TAG, "addObserver " + observed + " for " + timeLimit); + Slog.d(TAG, "addObserver " + Arrays.toString(observed) + " for " + timeLimit); } user.addUsageGroup(group); diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 0f034ad6a45e..b003f59d5e81 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -88,8 +88,8 @@ public final class DisconnectCause implements Parcelable { public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF"; /** - * Reason code (returned via {@link #getReason()}), which indicates that the video telephony - * call was disconnected because IMS access is blocked. + * Reason code (returned via {@link #getReason()}), which indicates that the call was + * disconnected because IMS access is blocked. */ public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED"; diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java index 677fe2f188a9..62eac7ac942a 100755 --- a/telephony/common/com/google/android/mms/pdu/PduParser.java +++ b/telephony/common/com/google/android/mms/pdu/PduParser.java @@ -793,7 +793,7 @@ public class PduParser { try { if (LOCAL_LOGV) { Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField + - contentType.toString()); + Arrays.toString(contentType)); } headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); } catch(NullPointerException e) { diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java index d978f5701eca..212aaaed46b2 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -433,7 +433,8 @@ public final class PhysicalChannelConfig implements Parcelable { return Objects.hash( mCellConnectionStatus, mCellBandwidthDownlinkKhz, mCellBandwidthUplinkKhz, mNetworkType, mFrequencyRange, mDownlinkChannelNumber, mUplinkChannelNumber, - mContextIds, mPhysicalCellId, mBand, mDownlinkFrequency, mUplinkFrequency); + Arrays.hashCode(mContextIds), mPhysicalCellId, mBand, mDownlinkFrequency, + mUplinkFrequency); } public static final diff --git a/telephony/java/android/telephony/SignalThresholdInfo.java b/telephony/java/android/telephony/SignalThresholdInfo.java index ae7d20929d58..3c1824539d1f 100644 --- a/telephony/java/android/telephony/SignalThresholdInfo.java +++ b/telephony/java/android/telephony/SignalThresholdInfo.java @@ -574,8 +574,8 @@ public final class SignalThresholdInfo implements Parcelable { @Override public int hashCode() { - return Objects.hash(mRan, mSignalMeasurementType, mHysteresisMs, mHysteresisDb, mThresholds, - mIsEnabled); + return Objects.hash(mRan, mSignalMeasurementType, mHysteresisMs, mHysteresisDb, + Arrays.hashCode(mThresholds), mIsEnabled); } public static final @NonNull Parcelable.Creator<SignalThresholdInfo> CREATOR = diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index cbd03c7f653a..d670e5592c42 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -1935,7 +1935,7 @@ public final class SmsManager { + " at calling enableCellBroadcastRangeForSubscriber. subId = " + subId); } } catch (RemoteException ex) { - Rlog.d(TAG, "enableCellBroadcastRange: " + ex.getStackTrace()); + Rlog.d(TAG, "enableCellBroadcastRange: ", ex); // ignore it } @@ -1996,7 +1996,7 @@ public final class SmsManager { + " at calling disableCellBroadcastRangeForSubscriber. subId = " + subId); } } catch (RemoteException ex) { - Rlog.d(TAG, "disableCellBroadcastRange: " + ex.getStackTrace()); + Rlog.d(TAG, "disableCellBroadcastRange: ", ex); // ignore it } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index c36eb2f9cf65..cb985bf2cda5 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -967,9 +967,9 @@ public class SubscriptionInfo implements Parcelable { public int hashCode() { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, - mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled, - mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, - mUsageSetting); + mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules), + mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner, + mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting); } @Override diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index e0c529848aae..19f2a9bc67a3 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -171,7 +171,7 @@ public final class TelephonyScanManager { ci[i] = (CellInfo) parcelables[i]; } executor.execute(() -> { - Rlog.d(TAG, "onResults: " + ci.toString()); + Rlog.d(TAG, "onResults: " + Arrays.toString(ci)); callback.onResults(Arrays.asList(ci)); }); } catch (Exception e) { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index db4898492ac5..661dd845757b 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -33,6 +33,8 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; +import java.util.Arrays; + public class MainInteractionSession extends VoiceInteractionSession implements View.OnClickListener { static final String TAG = "MainInteractionSession"; @@ -403,7 +405,7 @@ public class MainInteractionSession extends VoiceInteractionSession @Override public void onRequestPickOption(PickOptionRequest request) { Log.i(TAG, "onPickOption: prompt=" + request.getVoicePrompt() + " options=" - + request.getOptions() + " extras=" + request.getExtras()); + + Arrays.toString(request.getOptions()) + " extras=" + request.getExtras()); mConfirmButton.setText("Pick Option"); mPendingRequest = request; setPrompt(request.getVoicePrompt()); diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java index 733f602f6c14..8ae7186461d3 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java @@ -24,6 +24,8 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import java.util.Arrays; + public class StartVoiceInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "LocalVoiceInteractionActivity"; @@ -187,7 +189,8 @@ public class StartVoiceInteractionActivity extends Activity implements View.OnCl } @Override public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { - Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + Log.i(TAG, "Pick result: finished=" + finished + + " selections=" + Arrays.toString(selections) + " result=" + result); StringBuilder sb = new StringBuilder(); if (finished) { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index ada0e21e059a..4fc3a15ac38d 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -28,6 +28,8 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import java.util.Arrays; + public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; @@ -240,7 +242,8 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis } @Override public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { - Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + Log.i(TAG, "Pick result: finished=" + finished + + " selections=" + Arrays.toString(selections) + " result=" + result); StringBuilder sb = new StringBuilder(); if (finished) { diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index 9ad6d53a3246..a826646f69f3 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -48,14 +48,13 @@ public class TestLooper { private static final Method MESSAGE_MARK_IN_USE_METHOD; private static final String TAG = "TestLooper"; - private final MessageQueue.Clock mClock; + private final Clock mClock; private AutoDispatchThread mAutoDispatchThread; static { try { - LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE, - MessageQueue.Clock.class); + LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); LOOPER_CONSTRUCTOR.setAccessible(true); THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); @@ -84,15 +83,15 @@ public class TestLooper { * thread. * * Messages are dispatched when their {@link Message#when} is before or at {@link - * MessageQueue.Clock#uptimeMillis()}. + * Clock#uptimeMillis()}. * Use a custom clock with care. When using an offsettable clock like {@link * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock * and call {@link #dispatchAll()}. */ - public TestLooper(MessageQueue.Clock clock) { + public TestLooper(Clock clock) { try { - mLooper = LOOPER_CONSTRUCTOR.newInstance(false, clock); + mLooper = LOOPER_CONSTRUCTOR.newInstance(false); ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD .get(null); @@ -225,6 +224,10 @@ public class TestLooper { return count; } + public interface Clock { + long uptimeMillis(); + } + /** * Thread used to dispatch messages when the main thread is blocked waiting for a response. */ diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index dfa229173373..f9e52b491413 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -33,6 +33,7 @@ #include "ValueVisitor.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" #include "idmap2/Policies.h" #include "text/Printer.h" #include "util/Util.h" @@ -515,7 +516,8 @@ class XmlPrinter : public xml::ConstVisitor { } void Visit(const xml::Text* text) override { - printer_->Println(StringPrintf("T: '%s'", text->text.c_str())); + printer_->Println( + StringPrintf("T: '%s'", android::ResTable::normalizeForOutput(text->text.c_str()).c_str())); } private: diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 46a846bce35f..4fb7ed19ed20 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -369,9 +369,13 @@ class PackageFlattener { bool sparse_encode = use_sparse_entries_; - // Only sparse encode if the entries will be read on platforms O+. - sparse_encode = - sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O); + if (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0) { + // Sparse encode if sdk version is not set in context and config. + } else { + // Otherwise, only sparse encode if the entries will be read on platforms S_V2+. + sparse_encode = sparse_encode && + (context_->GetMinSdkVersion() >= SDK_S_V2 || config.sdkVersion >= SDK_S_V2); + } // Only sparse encode if the offsets are representable in 2 bytes. sparse_encode = diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index e48fca61fef8..f551bf61dc06 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -330,7 +330,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() .SetCompilationPackage("android") .SetPackageId(0x01) - .SetMinSdkVersion(SDK_O) + .SetMinSdkVersion(SDK_S_V2) .Build(); const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); @@ -376,7 +376,26 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { .SetMinSdkVersion(SDK_LOLLIPOP) .Build(); - const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26"); + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v32"); + auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); + + TableFlattenerOptions options; + options.use_sparse_entries = true; + + std::string no_sparse_contents; + ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); + + std::string sparse_contents; + ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); + + EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); +} + +TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build(); + + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; @@ -389,6 +408,27 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); + + // Attempt to parse the sparse contents. + + ResourceTable sparse_table; + BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"), + sparse_contents.data(), sparse_contents.size()); + ASSERT_TRUE(parser.Parse()); + + auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0", + sparse_config); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(0u, value->value.data); + + ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", + sparse_config), + IsNull()); + + value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", + sparse_config); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(4u, value->value.data); } TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { |