diff options
83 files changed, 1835 insertions, 931 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 1667c1658a07..6122ef254855 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -23,6 +23,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.Is.is; +import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; @@ -121,6 +122,12 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { @AfterClass public static void tearDownClass() { sSetUpClassException = null; + try { + // Recents activity may stop app switches. Restore the state to avoid affecting + // the next test. + ActivityManager.resumeAppSwitches(); + } catch (RemoteException ignored) { + } sUiAutomation.dropShellPermissionIdentity(); } diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index 8139a2e963c5..f04e55567520 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -88,10 +88,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { public void testRelayout() throws Throwable { final Activity activity = mActivityRule.getActivity(); final ContentView contentView = new ContentView(activity); - mActivityRule.runOnUiThread(() -> { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - activity.setContentView(contentView); - }); + mActivityRule.runOnUiThread(() -> activity.setContentView(contentView)); getInstrumentation().waitForIdleSync(); final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(), diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java index 9e17e940a06b..655d2f7f8aa7 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java @@ -19,11 +19,13 @@ package android.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import android.app.Activity; +import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.perftests.utils.PerfTestActivity; import android.provider.Settings; @@ -61,24 +63,32 @@ public class WindowManagerPerfTestBase { @BeforeClass public static void setUpOnce() { final Context context = getInstrumentation().getContext(); - sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); - // Keep the device awake during testing. - setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + sOriginalStayOnWhilePluggedIn = -1; + if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) { + sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn; + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY); + } if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); } - // In order to be closer to the real use case. - executeShellCommand("input keyevent KEYCODE_WAKEUP"); - executeShellCommand("wm dismiss-keyguard"); + if (!context.getSystemService(PowerManager.class).isInteractive() + || context.getSystemService(KeyguardManager.class).isKeyguardLocked()) { + executeShellCommand("input keyevent KEYCODE_WAKEUP"); + executeShellCommand("wm dismiss-keyguard"); + } context.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @AfterClass public static void tearDownOnce() { - setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + if (sOriginalStayOnWhilePluggedIn != -1) { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } } private static void setStayOnWhilePluggedIn(int value) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java index 91b954bd8af8..ba0fab6b4bc5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java @@ -44,7 +44,7 @@ import java.util.Objects; /** * Class for representing how a blob can be shared. * - * Note that this class is not thread-safe, callers need to take of synchronizing access. + * Note that this class is not thread-safe, callers need to take care of synchronizing access. */ class BlobAccessMode { @Retention(RetentionPolicy.SOURCE) diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index 3d06083a054a..7e8c90632fd9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -63,6 +63,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -351,14 +353,16 @@ class BlobMetadata { } catch (ErrnoException e) { throw e.rethrowAsIOException(); } - synchronized (mMetadataLock) { - return createRevocableFdLocked(fd, callingPackage); + try { + return createRevocableFd(fd, callingPackage); + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw e; } } - @GuardedBy("mMetadataLock") @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd, + private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, String callingPackage) throws IOException { final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index c2bf3e4cdea1..51cf805aa000 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -60,6 +60,8 @@ import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -208,27 +210,37 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to write in state: " + stateToString(mState)); } + } - try { - return openWriteLocked(offsetBytes, lengthBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openWriteInternal(offsetBytes, lengthBytes); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to write in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openWriteLocked(@BytesLong long offsetBytes, + private FileDescriptor openWriteInternal(@BytesLong long offsetBytes, @BytesLong long lengthBytes) throws IOException { // TODO: Add limit on active open sessions/writes/reads - FileDescriptor fd = null; try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); if (offsetBytes > 0) { final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET); if (curOffset != offsetBytes) { @@ -239,10 +251,10 @@ class BlobStoreSession extends IBlobStoreSession.Stub { if (lengthBytes > 0) { mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes); } + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -254,29 +266,40 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to read in state: " + stateToString(mState)); } + } - try { - return openReadLocked(); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openReadInternal(); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to read in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openReadLocked() throws IOException { - FileDescriptor fd = null; + private FileDescriptor openReadInternal() throws IOException { try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -397,7 +420,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } mState = state; - revokeAllFdsLocked(); + revokeAllFds(); if (sendCallback) { mListener.onStateChanged(this); @@ -437,20 +460,17 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } } - @GuardedBy("mSessionLock") - private void revokeAllFdsLocked() { - for (int i = mRevocableFds.size() - 1; i >= 0; --i) { - mRevocableFds.get(i).revoke(); + private void revokeAllFds() { + synchronized (mRevocableFds) { + for (int i = mRevocableFds.size() - 1; i >= 0; --i) { + mRevocableFds.get(i).revoke(); + } + mRevocableFds.clear(); } - mRevocableFds.clear(); } @GuardedBy("mSessionLock") - @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd) - throws IOException { - final RevocableFileDescriptor revocableFd = - new RevocableFileDescriptor(mContext, fd); + private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) { synchronized (mRevocableFds) { mRevocableFds.add(revocableFd); } @@ -459,7 +479,6 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mRevocableFds.remove(revocableFd); } }); - return revocableFd.getRevocableFileDescriptor(); } @Nullable diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 280a6870a5e1..062108757349 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -233,7 +233,7 @@ public class AppStandbyController implements AppStandbyInternal { * Set of system apps that are headless (don't have any declared activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. */ - @GuardedBy("mAppIdleLock") + @GuardedBy("mHeadlessSystemApps") private final ArrayMap<String, Boolean> mHeadlessSystemApps = new ArrayMap<>(); private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1); @@ -447,7 +447,8 @@ public class AppStandbyController implements AppStandbyInternal { userFileExists = mAppIdleHistory.userFileExists(UserHandle.USER_SYSTEM); } - loadHeadlessSystemAppCache(); + // Offload to handler thread to avoid boottime impact. + mHandler.post(this::loadHeadlessSystemAppCache); if (mPendingInitializeDefaults || !userFileExists) { initializeDefaultsForSystemApps(UserHandle.USER_SYSTEM); @@ -1121,7 +1122,9 @@ public class AppStandbyController implements AppStandbyInternal { } private boolean isHeadlessSystemApp(String packageName) { - return mHeadlessSystemApps.containsKey(packageName); + synchronized (mHeadlessSystemApps) { + return mHeadlessSystemApps.containsKey(packageName); + } } @Override @@ -1697,19 +1700,24 @@ public class AppStandbyController implements AppStandbyInternal { userId); evaluateSystemAppException(pi); } catch (PackageManager.NameNotFoundException e) { - mHeadlessSystemApps.remove(packageName); + synchronized (mHeadlessSystemApps) { + mHeadlessSystemApps.remove(packageName); + } } } - private void evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { - if (pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.isSystemApp()) { - synchronized (mAppIdleLock) { - if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { - // Headless system app. - mHeadlessSystemApps.put(pkgInfo.packageName, true); - } else { - mHeadlessSystemApps.remove(pkgInfo.packageName); - } + /** Returns true if the exception status changed. */ + private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { + if (pkgInfo == null || pkgInfo.applicationInfo == null + || !pkgInfo.applicationInfo.isSystemApp()) { + return false; + } + synchronized (mHeadlessSystemApps) { + if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { + // Headless system app. + return mHeadlessSystemApps.put(pkgInfo.packageName, true) == null; + } else { + return mHeadlessSystemApps.remove(pkgInfo.packageName) != null; } } } @@ -1754,7 +1762,12 @@ public class AppStandbyController implements AppStandbyInternal { UserHandle.USER_SYSTEM); final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { - evaluateSystemAppException(packages.get(i)); + PackageInfo pkgInfo = packages.get(i); + if (pkgInfo != null && evaluateSystemAppException(pkgInfo)) { + mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, + UserHandle.USER_SYSTEM, -1, pkgInfo.packageName) + .sendToTarget(); + } } } @@ -1852,9 +1865,12 @@ public class AppStandbyController implements AppStandbyInternal { pw.println(); pw.println("mHeadlessSystemApps=["); - for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { - pw.print(mHeadlessSystemApps.keyAt(i)); - pw.println(","); + synchronized (mHeadlessSystemApps) { + for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mHeadlessSystemApps.keyAt(i)); + pw.println(","); + } } pw.println("]"); pw.println(); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 47bab2947aaf..6f952f637506 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -267,8 +267,11 @@ void StatsService::dumpIncidentSection(int out) { for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { uint64_t reportsListToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); + // Don't include the current bucket to avoid skipping buckets. + // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS + // or other alternatives to avoid skipping buckets for pulled metrics. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), - true /* includeCurrentBucket */, false /* erase_data */, + false /* includeCurrentBucket */, false /* erase_data */, ADB_DUMP, FAST, &proto); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 8203f38de393..9dcba7cad9a8 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -959,7 +959,10 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); int64_t bucketEndTime = fullBucketEndTimeNs; int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - if (numBucketsForward > 1) { + + // Skip buckets if this is a pulled metric or a pushed metric that is diffed. + if (numBucketsForward > 1 && (mIsPulled || mUseDiff)) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); // Something went wrong. Maybe the device was sleeping for a long time. It is better diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp index db402a0dd658..32cecd3b9dbc 100644 --- a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp +++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -50,13 +50,13 @@ TEST(MultiConditionTrigger, TestMultipleConditions) { }); vector<thread> threads; - vector<bool> done(numConditions, false); + vector<int> done(numConditions, 0); int i = 0; for (const string& conditionName : conditionNames) { threads.emplace_back([&done, &conditionName, &trigger, i] { sleep_for(chrono::milliseconds(3)); - done[i] = true; + done[i] = 1; trigger.markComplete(conditionName); }); i++; diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index d22f94213338..3f03f2a3e754 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -71,6 +71,4 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); - - void removeImeSurface(); } diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 4bf0fca445d1..cf967c02bec5 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -118,4 +118,7 @@ <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item> <item>com.android.systemui.car.volume.VolumeUI</item> </string-array> + + <!-- How many milliseconds to wait before force hiding the UserSwitchTransitionView --> + <integer name="config_userSwitchTransitionViewShownTimeoutMs" translatable="false">5000</integer> </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java index 8aa7b6389d85..45f3d342fb6e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java @@ -33,6 +33,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.R; import com.android.systemui.car.window.OverlayViewController; @@ -49,12 +50,22 @@ import javax.inject.Singleton; public class UserSwitchTransitionViewController extends OverlayViewController { private static final String TAG = "UserSwitchTransition"; private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true"; + private static final boolean DEBUG = false; private final Context mContext; private final Handler mHandler; private final Resources mResources; private final UserManager mUserManager; private final IWindowManager mWindowManagerService; + private final int mWindowShownTimeoutMs; + private final Runnable mWindowShownTimeoutCallback = () -> { + if (DEBUG) { + Log.w(TAG, "Window was not hidden within " + getWindowShownTimeoutMs() + " ms, so it" + + "was hidden by mWindowShownTimeoutCallback."); + } + + handleHide(); + }; @GuardedBy("this") private boolean mShowing; @@ -76,6 +87,8 @@ public class UserSwitchTransitionViewController extends OverlayViewController { mResources = resources; mUserManager = userManager; mWindowManagerService = windowManagerService; + mWindowShownTimeoutMs = mResources.getInteger( + R.integer.config_userSwitchTransitionViewShownTimeoutMs); } /** @@ -98,6 +111,9 @@ public class UserSwitchTransitionViewController extends OverlayViewController { populateDialog(mPreviousUserId, newUserId); // next time a new user is selected, this current new user will be the previous user. mPreviousUserId = newUserId; + // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the + // window and log a warning message. + mHandler.postDelayed(mWindowShownTimeoutCallback, mWindowShownTimeoutMs); }); } @@ -105,6 +121,12 @@ public class UserSwitchTransitionViewController extends OverlayViewController { if (!mShowing) return; mShowing = false; mHandler.post(this::stop); + mHandler.removeCallbacks(mWindowShownTimeoutCallback); + } + + @VisibleForTesting + int getWindowShownTimeoutMs() { + return mWindowShownTimeoutMs; } private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) { diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java index 65c556269f13..797dbf515b7e 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java @@ -18,6 +18,8 @@ package com.android.systemui.car.userswitcher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.content.Context; @@ -122,6 +124,29 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { any()); } + @Test + public void onWindowShownTimeoutPassed_viewNotHidden_hidesUserSwitchTransitionView() { + mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1); + reset(mOverlayViewGlobalStateController); + + getContext().getMainThreadHandler().postDelayed(() -> { + verify(mOverlayViewGlobalStateController).hideView( + eq(mCarUserSwitchingDialogController), any()); + }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10); + } + + @Test + public void onWindowShownTimeoutPassed_viewHidden_doesNotHideUserSwitchTransitionViewAgain() { + mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1); + mCarUserSwitchingDialogController.handleHide(); + reset(mOverlayViewGlobalStateController); + + getContext().getMainThreadHandler().postDelayed(() -> { + verify(mOverlayViewGlobalStateController, never()).hideView( + eq(mCarUserSwitchingDialogController), any()); + }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10); + } + private final class TestableUserSwitchTransitionViewController extends UserSwitchTransitionViewController { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 85a9fec859f3..fffcafbf88fb 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -34,7 +34,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 7; + int VERSION = 8; String TAG = "QS"; @@ -67,15 +67,12 @@ public interface QS extends FragmentBase { } /** - * We need this to handle nested scrolling for QS.. - * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the - * scroll containers are using the same touch slop, they try to start scrolling at the - * same time and NotificationPanelView wins, this lets QS win. - * - * TODO: Do this using NestedScroll capabilities. + * Should touches from the notification panel be disallowed? + * The notification panel might grab any touches rom QS at any time to collapse the shade. + * We should disallow that in case we are showing the detail panel. */ - default boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); + default boolean disallowPanelTouches() { + return isShowingDetail(); } @ProvidesInterface(version = HeightListener.VERSION) diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index 294bd50fcf8b..59e1a755d7d2 100644 --- a/packages/SystemUI/res/layout/qs_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -22,6 +22,7 @@ android:background="@drawable/qs_detail_background" android:clickable="true" android:orientation="vertical" + android:layout_marginTop="@*android:dimen/quick_qs_offset_height" android:paddingBottom="8dp" android:visibility="invisible" android:elevation="4dp" @@ -44,7 +45,7 @@ android:scaleType="fitXY" /> - <com.android.systemui.qs.NonInterceptingScrollView + <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -54,7 +55,7 @@ android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent"/> - </com.android.systemui.qs.NonInterceptingScrollView> + </ScrollView> <include layout="@layout/qs_detail_buttons" /> diff --git a/packages/SystemUI/res/layout/qs_divider.xml b/packages/SystemUI/res/layout/qs_divider.xml deleted file mode 100644 index 39d48ea4746e..000000000000 --- a/packages/SystemUI/res/layout/qs_divider.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 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. ---> -<View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="1dp" - android:alpha=".12" - android:background="?android:attr/colorForeground" /> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index ebfd0a0fd537..5c00af5705e9 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -61,7 +61,10 @@ android:clickable="true" android:gravity="center_vertical" android:focusable="true" + android:singleLine="true" + android:ellipsize="end" android:textAppearance="@style/TextAppearance.QS.Status" + android:layout_marginEnd="4dp" android:visibility="gone"/> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index a8960d9b9437..5c8b2b08324f 100644 --- a/packages/SystemUI/res/layout/qs_paged_page.xml +++ b/packages/SystemUI/res/layout/qs_paged_page.xml @@ -20,7 +20,5 @@ android:id="@+id/tile_page" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="@dimen/notification_side_paddings" - android:paddingEnd="@dimen/notification_side_paddings" android:clipChildren="false" android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index cdf84260e399..761ab03ee87e 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -48,18 +48,22 @@ android:clipChildren="false" android:background="@drawable/qs_bg_gradient" /> - - <com.android.systemui.qs.QSPanel - android:id="@+id/quick_settings_panel" - android:layout_marginTop="@*android:dimen/quick_qs_offset_height" + <com.android.systemui.qs.NonInterceptingScrollView + android:id="@+id/expanded_qs_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="4dp" - android:background="@android:color/transparent" - android:focusable="true" - android:accessibilityTraversalBefore="@android:id/edit"> - <include layout="@layout/qs_footer_impl" /> - </com.android.systemui.qs.QSPanel> + android:layout_weight="1"> + <com.android.systemui.qs.QSPanel + android:id="@+id/quick_settings_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:focusable="true" + android:accessibilityTraversalBefore="@android:id/edit"> + <include layout="@layout/qs_footer_impl" /> + </com.android.systemui.qs.QSPanel> + </com.android.systemui.qs.NonInterceptingScrollView> <include layout="@layout/quick_status_bar_expanded_header" /> diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 15f398aa52e6..e7c7b5fbf890 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" +<com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:paddingBottom="@dimen/qs_tile_padding_top" @@ -23,23 +23,28 @@ android:paddingStart="@dimen/qs_footer_padding_start" android:paddingEnd="@dimen/qs_footer_padding_end" android:gravity="center_vertical" + android:layout_gravity="center_vertical|center_horizontal" android:background="@android:color/transparent"> - <TextView + <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/footer_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start" android:layout_weight="1" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" android:textAppearance="@style/TextAppearance.QS.TileLabel" - style="@style/qs_security_footer"/> + android:textColor="?android:attr/textColorPrimary"/> <ImageView android:id="@+id/footer_icon" android:layout_width="@dimen/qs_footer_icon_size" android:layout_height="@dimen/qs_footer_icon_size" + android:layout_marginStart="8dp" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - style="@style/qs_security_footer"/> + android:tint="?android:attr/textColorPrimary" /> -</LinearLayout> +</com.android.systemui.util.NeverExactlyLinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index e6ef9b4b902c..fb82304663aa 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -20,11 +20,12 @@ android:layout_height="@dimen/qs_header_tooltip_height" android:layout_below="@id/quick_status_bar_system_icons" android:visibility="invisible" - android:theme="@style/QSHeaderTheme"> + android:theme="@style/QSHeaderTheme" + android:forceHasOverlappingRendering="false"> <com.android.systemui.qs.QSHeaderInfoLayout android:id="@+id/status_container" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index abeb33111c40..dc34127496f6 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -29,7 +29,6 @@ android:clipToPadding="false" android:paddingTop="0dp" android:paddingEnd="0dp" - android:paddingBottom="10dp" android:paddingStart="0dp" android:elevation="4dp" > @@ -52,6 +51,7 @@ android:clipChildren="false" android:clipToPadding="false" android:focusable="true" + android:paddingBottom="10dp" android:importantForAccessibility="yes" /> <TextView diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index d118d8956f17..2d42ce6faa2c 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -31,7 +31,6 @@ <dimen name="battery_detail_graph_space_bottom">9dp</dimen> <integer name="quick_settings_num_columns">4</integer> - <bool name="quick_settings_wide">true</bool> <dimen name="qs_detail_margin_top">0dp</dimen> <dimen name="volume_tool_tip_right_margin">136dp</dimen> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 50261e1b2139..4fdeb6fa4a92 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,9 +29,4 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white --> - <item name="android:tint">#FFFFFFFF</item> - </style> - </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index c4c467152281..3b00ad1bf0c4 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -15,8 +15,5 @@ ~ limitations under the License --> <resources> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <integer name="quick_settings_num_columns">3</integer> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml new file mode 100644 index 000000000000..40838f362f5c --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <!-- Size of the panel of large phones on portrait. This shouldn't fill, but have some padding on the side --> + <dimen name="notification_panel_width">416dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 8f73d231c732..fdf4e3b1b796 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -16,8 +16,6 @@ */ --> <resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">416dp</dimen> <!-- Diameter of outer shape drawable shown in navbar search--> <dimen name="navbar_search_outerring_diameter">430dip</dimen> diff --git a/packages/SystemUI/res/values-sw900dp-land/dimen.xml b/packages/SystemUI/res/values-sw900dp-land/dimen.xml deleted file mode 100644 index 1e0600ed5fe0..000000000000 --- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * 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. -*/ ---> -<resources> - <!-- Standard notification width + gravity for tablet large screen device --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> - diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml index 16d5317636a2..a33f1312521f 100644 --- a/packages/SystemUI/res/values-w550dp-land/config.xml +++ b/packages/SystemUI/res/values-w550dp-land/config.xml @@ -20,9 +20,5 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> <resources> - - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">true</bool> - - <integer name="quick_settings_num_columns">4</integer> + <integer name="quick_settings_num_columns">6</integer> </resources> diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml deleted file mode 100644 index 017ca6987820..000000000000 --- a/packages/SystemUI/res/values-w550dp-land/dimens.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (c) 2016, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ ---> -<resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml new file mode 100644 index 000000000000..108d6cf16fec --- /dev/null +++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <!-- Standard notification width + gravity --> + <dimen name="notification_panel_width">644dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 4fc904b21741..40a4b5074413 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -203,8 +203,8 @@ <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black --> <!-- Bubbles --> - <color name="bubbles_pointer_light">#FFFFFF</color> - <color name="bubbles_pointer_dark">@color/GM2_grey_800</color> + <color name="bubbles_light">#FFFFFF</color> + <color name="bubbles_dark">@color/GM2_grey_800</color> <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 00537ff0466d..848cdb1e831c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -95,9 +95,6 @@ <!-- The maximum number of tiles in the QuickQSPanel --> <integer name="quick_qs_panel_max_columns">6</integer> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 44d3b02f0db7..d176fed57459 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1075,8 +1075,7 @@ <dimen name="edge_margin">8dp</dimen> <!-- The absolute side margins of quick settings --> - <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen> - <dimen name="quick_settings_media_extra_bottom_margin">6dp</dimen> + <dimen name="quick_settings_bottom_margin_media">16dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> @@ -1264,8 +1263,8 @@ <dimen name="qs_media_panel_outer_padding">16dp</dimen> <dimen name="qs_media_album_size">52dp</dimen> <dimen name="qs_seamless_icon_size">20dp</dimen> - <dimen name="qqs_media_spacing">8dp</dimen> - <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen> + <dimen name="qqs_media_spacing">16dp</dimen> + <dimen name="qs_footer_horizontal_margin">22dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 8097c01cb042..68c2a38f53c3 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,11 +387,6 @@ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">?android:attr/textColorSecondary</item> - <item name="android:tint">?android:attr/textColorSecondary</item> - </style> - <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorAccent">@color/remote_input_accent</item> </style> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 34c85877577b..7914d864da0f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -79,6 +79,7 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; +import android.util.EventLog; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -1074,6 +1075,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; } + private boolean isUserEncryptedOrLockdown(int userId) { + // Biometrics should not be started in this case. Think carefully before modifying this + // method, see b/79776455 + final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId); + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); + return isLockDown || isEncrypted; + } + private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } @@ -1904,11 +1916,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean allowedOnBouncer = !(mFingerprintLockedOut && mBouncer && mCredentialAttempted); final int user = getCurrentUser(); - final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -1917,7 +1924,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer && !isLockDown && !isEncrypted; + && allowedOnBouncer && !isUserEncryptedOrLockdown(user); return shouldListen; } @@ -1931,11 +1938,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !statusBarShadeLocked; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - final boolean isEncrypted = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); final boolean isTimedOut = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); @@ -1958,7 +1960,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed && strongAuthAllowsScanning && mIsPrimaryUser - && !mSecureCameraLaunched && !isLockDown && !isEncrypted; + && !mSecureCameraLaunched && !isUserEncryptedOrLockdown(user); // Aggregate relevant fields for debug logging. if (DEBUG_FACE || DEBUG_SPEW) { @@ -2031,6 +2033,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (mFingerprintCancelSignal != null) { mFingerprintCancelSignal.cancel(); } + + if (isUserEncryptedOrLockdown(userId)) { + // If this happens, shouldListenForFingerprint() is wrong. SafetyNet for b/79776455 + EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFingerprint"); + } mFingerprintCancelSignal = new CancellationSignal(); mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback, null, userId); @@ -2049,6 +2056,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (mFaceCancelSignal != null) { mFaceCancelSignal.cancel(); } + + if (isUserEncryptedOrLockdown(userId)) { + // If this happens, shouldListenForFace() is wrong. SafetyNet for b/79776455 + EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFace"); + } mFaceCancelSignal = new CancellationSignal(); mFaceManager.authenticate(null, mFaceCancelSignal, 0, mFaceAuthenticationCallback, null, userId); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 7f78ddf2cf1c..6da7bc8a2ade 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -15,7 +15,6 @@ */ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; @@ -29,21 +28,19 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Path; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.graphics.drawable.Icon; import android.os.UserHandle; import android.provider.Settings; -import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -57,17 +54,12 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - /** - * NotificationEntry associated with the bubble. A null value implies this bubble is loaded - * from disk. - */ - @Nullable - private NotificationEntry mEntry; private final String mKey; private long mLastUpdated; private long mLastAccessed; + @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ @@ -75,8 +67,6 @@ class Bubble implements BubbleViewProvider { /** Whether flyout text should be suppressed, regardless of any other flags or state. */ private boolean mSuppressFlyout; - /** Whether this bubble should auto expand regardless of the normal flag, used for overflow. */ - private boolean mShouldAutoExpand; // Items that are typically loaded later private String mAppName; @@ -92,6 +82,7 @@ class Bubble implements BubbleViewProvider { * Presentational info about the flyout. */ public static class FlyoutMessage { + @Nullable public Icon senderIcon; @Nullable public Drawable senderAvatar; @Nullable public CharSequence senderName; @Nullable public CharSequence message; @@ -109,16 +100,39 @@ class Bubble implements BubbleViewProvider { private UserHandle mUser; @NonNull private String mPackageName; + @Nullable + private String mTitle; + @Nullable + private Icon mIcon; + private boolean mIsBubble; + private boolean mIsVisuallyInterruptive; + private boolean mIsClearable; + private boolean mShouldSuppressNotificationDot; + private boolean mShouldSuppressNotificationList; + private boolean mShouldSuppressPeek; private int mDesiredHeight; @DimenRes private int mDesiredHeightResId; + /** for logging **/ + @Nullable + private InstanceId mInstanceId; + @Nullable + private String mChannelId; + private int mNotificationId; + private int mAppUid = -1; + + @Nullable + private PendingIntent mIntent; + @Nullable + private PendingIntent mDeleteIntent; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, - final int desiredHeight, final int desiredHeightResId) { + final int desiredHeight, final int desiredHeightResId, @Nullable final String title) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; @@ -126,8 +140,10 @@ class Bubble implements BubbleViewProvider { mFlags = 0; mUser = shortcutInfo.getUserHandle(); mPackageName = shortcutInfo.getPackage(); + mIcon = shortcutInfo.getIcon(); mDesiredHeight = desiredHeight; mDesiredHeightResId = desiredHeightResId; + mTitle = title; } /** Used in tests when no UI is required. */ @@ -145,12 +161,6 @@ class Bubble implements BubbleViewProvider { return mKey; } - @Nullable - public NotificationEntry getEntry() { - return mEntry; - } - - @NonNull public UserHandle getUser() { return mUser; } @@ -203,14 +213,7 @@ class Bubble implements BubbleViewProvider { @Nullable public String getTitle() { - final CharSequence titleCharSeq; - if (mEntry == null) { - titleCharSeq = null; - } else { - titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); - } - return titleCharSeq != null ? titleCharSeq.toString() : null; + return mTitle; } /** @@ -331,17 +334,44 @@ class Bubble implements BubbleViewProvider { void setEntry(@NonNull final NotificationEntry entry) { Objects.requireNonNull(entry); Objects.requireNonNull(entry.getSbn()); - mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); - mFlags = entry.getSbn().getNotification().flags; + mIsBubble = entry.getSbn().getNotification().isBubbleNotification(); mPackageName = entry.getSbn().getPackageName(); mUser = entry.getSbn().getUser(); + mTitle = getTitle(entry); + mIsClearable = entry.isClearable(); + mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); + mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); + mShouldSuppressPeek = entry.shouldSuppressPeek(); + mChannelId = entry.getSbn().getNotification().getChannelId(); + mNotificationId = entry.getSbn().getId(); + mAppUid = entry.getSbn().getUid(); + mInstanceId = entry.getSbn().getInstanceId(); + mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry); + if (entry.getRanking() != null) { + mShortcutInfo = entry.getRanking().getShortcutInfo() != null + ? entry.getRanking().getShortcutInfo() : mShortcutInfo; + mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + } if (entry.getBubbleMetadata() != null) { + mFlags = entry.getBubbleMetadata().getFlags(); mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + mIcon = entry.getBubbleMetadata().getIcon(); + mIntent = entry.getBubbleMetadata().getIntent(); + mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } } + @Nullable + Icon getIcon() { + return mIcon; + } + + boolean isVisuallyInterruptive() { + return mIsVisuallyInterruptive; + } + /** * @return the last time this bubble was updated or accessed, whichever is most recent. */ @@ -364,6 +394,19 @@ class Bubble implements BubbleViewProvider { return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; } + public InstanceId getInstanceId() { + return mInstanceId; + } + + @Nullable + public String getChannelId() { + return mChannelId; + } + + public int getNotificationId() { + return mNotificationId; + } + /** * Should be invoked whenever a Bubble is accessed (selected while expanded). */ @@ -384,24 +427,19 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - if (mEntry == null) return false; - return !shouldSuppressNotification() || !mEntry.isClearable(); + return !shouldSuppressNotification() || !mIsClearable; } /** * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { - if (mEntry == null) return; boolean prevShowInShade = showInShade(); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - int flags = data.getFlags(); if (suppressNotification) { - flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } else { - flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - data.setFlags(flags); if (showInShade() != prevShowInShade && mSuppressionListener != null) { mSuppressionListener.onBubbleNotificationSuppressionChange(this); @@ -424,9 +462,8 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { - if (mEntry == null) return false; return mShowBubbleUpdateDot - && !mEntry.shouldSuppressNotificationDot() + && !mShouldSuppressNotificationDot && !shouldSuppressNotification(); } @@ -434,10 +471,9 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { - if (mEntry == null) return false; - return !mSuppressFlyout && !mEntry.shouldSuppressPeek() + return !mSuppressFlyout && !mShouldSuppressPeek && !shouldSuppressNotification() - && !mEntry.shouldSuppressNotificationList(); + && !mShouldSuppressNotificationList; } /** @@ -480,25 +516,14 @@ class Bubble implements BubbleViewProvider { } } - /** - * Whether shortcut information should be used to populate the bubble. - * <p> - * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}. - * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. - */ - boolean usingShortcutInfo() { - return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null - || mShortcutInfo != null; + @Nullable + PendingIntent getBubbleIntent() { + return mIntent; } @Nullable - PendingIntent getBubbleIntent() { - if (mEntry == null) return null; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - if (data != null) { - return data.getIntent(); - } - return null; + PendingIntent getDeleteIntent() { + return mDeleteIntent; } Intent getSettingsIntent(final Context context) { @@ -514,8 +539,12 @@ class Bubble implements BubbleViewProvider { return intent; } + public int getAppUid() { + return mAppUid; + } + private int getUid(final Context context) { - if (mEntry != null) return mEntry.getSbn().getUid(); + if (mAppUid != -1) return mAppUid; final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { @@ -548,24 +577,27 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return true; - return mEntry.getBubbleMetadata() != null - && mEntry.getBubbleMetadata().isNotificationSuppressed(); + return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); } - boolean shouldAutoExpand() { - if (mEntry == null) return mShouldAutoExpand; - Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); - return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; + public boolean shouldAutoExpand() { + return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } void setShouldAutoExpand(boolean shouldAutoExpand) { - mShouldAutoExpand = shouldAutoExpand; + if (shouldAutoExpand) { + enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } else { + disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } + } + + public void setIsBubble(final boolean isBubble) { + mIsBubble = isBubble; } public boolean isBubble() { - if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; - return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; + return mIsBubble; } public void enable(int option) { @@ -576,6 +608,10 @@ class Bubble implements BubbleViewProvider { mFlags &= ~option; } + public boolean isEnabled(int option) { + return (mFlags & option) != 0; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; @@ -610,34 +646,24 @@ class Bubble implements BubbleViewProvider { @Override public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { - if (this.getEntry() == null - || this.getEntry().getSbn() == null) { - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - null /* package name */, - null /* notification channel */, - 0 /* notification ID */, - 0 /* bubble position */, - bubbleCount, - action, - normalX, - normalY, - false /* unread bubble */, - false /* on-going bubble */, - false /* isAppForeground (unused) */); - } else { - StatusBarNotification notification = this.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - index, - bubbleCount, - action, - normalX, - normalY, - this.showInShade(), - false /* isOngoing (unused) */, - false /* isAppForeground (unused) */); - } + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + mPackageName, + mChannelId, + mNotificationId, + index, + bubbleCount, + action, + normalX, + normalY, + showInShade(), + false /* isOngoing (unused) */, + false /* isAppForeground (unused) */); + } + + @Nullable + private static String getTitle(@NonNull final NotificationEntry e) { + final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence( + Notification.EXTRA_TITLE); + return titleCharSeq == null ? null : titleCharSeq.toString(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c4c5da42ec06..b2c5402c7cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -505,8 +505,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override public void removeNotification(NotificationEntry entry, int reason) { - mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - reason); + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason); } @Override @@ -637,8 +636,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpandListener(mExpandListener); } - mStackView.setUnbubbleConversationCallback(notificationEntry -> - onUserChangedBubble(notificationEntry, false /* shouldBubble */)); + mStackView.setUnbubbleConversationCallback(key -> { + final NotificationEntry entry = + mNotificationEntryManager.getPendingOrActiveNotif(key); + if (entry != null) { + onUserChangedBubble(entry, false /* shouldBubble */); + } + }); } addToWindowManagerMaybe(); @@ -1024,10 +1028,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { - if (entry == null) { - return; - } + public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) { NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); @@ -1103,7 +1104,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData.removeSuppressedSummary(groupKey); // Remove any associated bubble children with the summary - final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } @@ -1161,21 +1163,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); - if (isBubble) { - b.enable(FLAG_BUBBLE); - } else { - b.disable(FLAG_BUBBLE); - } - if (b.getEntry() != null) { + b.setIsBubble(isBubble); + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(b.getKey()); + if (entry != null) { // Updating the entry to be a bubble will trigger our normal update flow - setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); + setIsBubble(entry, isBubble, b.shouldAutoExpand()); } else if (isBubble) { - // If we have no entry to update, it's a persisted bubble so - // we need to add it to the stack ourselves + // If bubble doesn't exist, it's a persisted bubble so we need to add it to the + // stack ourselves Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, !bubble.shouldAutoExpand() /* showInShade */); - } } @@ -1214,6 +1213,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason == DISMISS_NOTIF_CANCEL) { bubblesToBeRemovedFromRepository.add(bubble); } + final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( + bubble.getKey()); if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey()) && (!bubble.showInShade() @@ -1222,26 +1223,27 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - if (bubble.getEntry() != null) { - cb.removeNotification(bubble.getEntry(), REASON_CANCEL); + if (entry != null) { + cb.removeNotification(entry, REASON_CANCEL); } } } else { if (bubble.isBubble()) { setIsBubble(bubble, false /* isBubble */); } - if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { - bubble.getEntry().getRow().updateBubbleButton(); + if (entry != null && entry.getRow() != null) { + entry.getRow().updateBubbleButton(); } } } - if (bubble.getEntry() != null) { - final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + if (entry != null) { + final String groupKey = entry.getSbn().getGroupKey(); + if (mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager).isEmpty()) { // Time to potentially remove the summary for (NotifCallback cb : mCallbacks) { - cb.maybeCancelSummary(bubble.getEntry()); + cb.maybeCancelSummary(entry); } } } @@ -1266,9 +1268,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged && mStackView != null) { mStackView.setSelectedBubble(update.selectedBubble); - if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { - mNotificationGroupManager.updateSuppression( - update.selectedBubble.getEntry()); + if (update.selectedBubble != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(update.selectedBubble.getKey()); + if (entry != null) { + mNotificationGroupManager.updateSuppression(entry); + } } } @@ -1341,7 +1346,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } String groupKey = entry.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); @@ -1361,9 +1367,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // As far as group manager is concerned, once a child is no longer shown // in the shade, it is essentially removed. Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey()); - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */); + if (bubbleChild != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(bubbleChild.getKey()); + if (entry != null) { + mNotificationGroupManager.onEntryRemoved(entry); + } + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */); + } } else { // non-bubbled children can be removed for (NotifCallback cb : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 20a9a8cf324c..c8706126c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -22,7 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; -import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; @@ -34,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; @@ -256,8 +256,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= bubble.getEntry() == null - || !bubble.getEntry().getRanking().visuallyInterruptive(); + suppressFlyout |= !bubble.isVisuallyInterruptive(); if (prevBubble == null) { // Create a new bubble @@ -335,13 +334,15 @@ public class BubbleData { * Retrieves any bubbles that are part of the notification group represented by the provided * group key. */ - ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) { + ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull + NotificationEntryManager nem) { ArrayList<Bubble> bubbleChildren = new ArrayList<>(); if (groupKey == null) { return bubbleChildren; } for (Bubble b : mBubbles) { - if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { + final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey()); + if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { bubbleChildren.add(b); } } @@ -439,9 +440,7 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } - if (bubbleToRemove.getEntry() != null) { - maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); - } + maybeSendDeleteIntent(reason, bubbleToRemove); } void overflowBubble(@DismissReason int reason, Bubble bubble) { @@ -611,21 +610,14 @@ public class BubbleData { return true; } - private void maybeSendDeleteIntent(@DismissReason int reason, - @NonNull final NotificationEntry entry) { - if (reason == BubbleController.DISMISS_USER_GESTURE) { - Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); - PendingIntent deleteIntent = bubbleMetadata != null - ? bubbleMetadata.getDeleteIntent() - : null; - if (deleteIntent != null) { - try { - deleteIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send delete intent for bubble with key: " - + entry.getKey()); - } - } + private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) { + if (reason != BubbleController.DISMISS_USER_GESTURE) return; + PendingIntent deleteIntent = bubble.getDeleteIntent(); + if (deleteIntent == null) return; + try { + deleteIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey()); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index d20f40559b5d..0c25d144938c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -74,11 +74,15 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> - var shortcutId = b.shortcutInfo?.id - if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId - if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, - b.rawDesiredHeightResId) + BubbleEntity( + userId, + b.packageName, + b.shortcutInfo?.id ?: return@mapNotNull null, + b.key, + b.rawDesiredHeight, + b.rawDesiredHeightResId, + b.title + ) } } @@ -159,8 +163,13 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, - entity.desiredHeightResId) } + ?.let { shortcutInfo -> Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title + ) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 6dcc9dcdc63c..2f7ffde50fd4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -169,7 +169,7 @@ public class BubbleExpandedView extends LinearLayout { return; } try { - if (!mIsOverflow && mBubble.usingShortcutInfo()) { + if (!mIsOverflow && mBubble.getShortcutInfo() != null) { options.setApplyActivityFlagsForBubbles(true); mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), options, null /* sourceBounds */); @@ -437,10 +437,10 @@ public class BubbleExpandedView extends LinearLayout { getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (mode) { case Configuration.UI_MODE_NIGHT_NO: - mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_light)); + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_light)); break; case Configuration.UI_MODE_NIGHT_YES: - mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_dark)); + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_dark)); break; } mPointerView.setBackground(mPointerDrawable); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 8c76cda3290f..1fa3aaae5e61 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.text.TextUtils; import android.view.LayoutInflater; @@ -223,9 +224,10 @@ public class BubbleFlyoutView extends FrameLayout { float[] dotCenter, boolean hideDot) { - if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) { + final Drawable senderAvatar = flyoutMessage.senderAvatar; + if (senderAvatar != null && flyoutMessage.isGroupChat) { mSenderAvatar.setVisibility(VISIBLE); - mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar); + mSenderAvatar.setImageDrawable(senderAvatar); } else { mSenderAvatar.setVisibility(GONE); mSenderAvatar.setTranslationX(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 74231c648f00..a799f2d739e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -15,7 +15,8 @@ */ package com.android.systemui.bubbles; -import android.app.Notification; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -50,15 +51,14 @@ public class BubbleIconFactory extends BaseIconFactory { /** * Returns the drawable that the developer has provided to display in the bubble. */ - Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo, - Notification.BubbleMetadata metadata) { + Drawable getBubbleDrawable(@NonNull final Context context, + @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) { if (shortcutInfo != null) { LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); int density = context.getResources().getConfiguration().densityDpi; return launcherApps.getShortcutIconDrawable(shortcutInfo, density); } else { - Icon ic = metadata.getIcon(); if (ic != null) { if (ic.getType() == Icon.TYPE_URI || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java index c5faae0d703e..c1dd8c36ff6f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.bubbles; -import android.service.notification.StatusBarNotification; - import com.android.internal.logging.UiEventLoggerImpl; /** @@ -32,12 +30,11 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger * @param e UI event */ public void log(Bubble b, UiEventEnum e) { - if (b.getEntry() == null) { + if (b.getInstanceId() == null) { // Added from persistence -- TODO log this with specific event? return; } - StatusBarNotification sbn = b.getEntry().getSbn(); - logWithInstanceId(e, sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); + logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java index 0c62e9f9f548..dadcb3a3a7c4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java @@ -22,9 +22,9 @@ import static android.view.View.GONE; import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; import android.content.Context; -import android.content.res.TypedArray; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.drawable.AdaptiveIconDrawable; @@ -88,19 +88,23 @@ public class BubbleOverflow implements BubbleViewProvider { false /* attachToRoot */); mOverflowBtn.setContentDescription(mContext.getResources().getString( R.string.bubble_overflow_button_content_description)); + Resources res = mContext.getResources(); - TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.colorBackgroundFloating}); - int bgColor = ta.getColor(0, Color.WHITE /* default */); - ta.recycle(); - + // Set color for button icon and dot TypedValue typedValue = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); int colorAccent = mContext.getColor(typedValue.resourceId); mOverflowBtn.getDrawable().setTint(colorAccent); mDotColor = colorAccent; - ColorDrawable bg = new ColorDrawable(bgColor); + // Set color for button and activity background + ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light)); + final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (mode == Configuration.UI_MODE_NIGHT_YES) { + bg = new ColorDrawable(res.getColor(R.color.bubbles_dark)); + } + + // Apply icon inset InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), mBitmapSize - mIconBitmapSize /* inset */); AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg); @@ -110,6 +114,7 @@ public class BubbleOverflow implements BubbleViewProvider { null /* user */, true /* shrinkNonAdaptiveIcons */).icon; + // Get path with dot location float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(), null /* outBounds */, null /* path */, null /* outMaskShape */); float radius = DEFAULT_PATH_SIZE / 2f; @@ -120,14 +125,9 @@ public class BubbleOverflow implements BubbleViewProvider { radius /* pivot y */); mPath.transform(matrix); - mOverflowBtn.setVisibility(GONE); mOverflowBtn.setRenderedBubble(this); } - ImageView getBtn() { - return mOverflowBtn; - } - void setVisible(int visible) { mOverflowBtn.setVisibility(visible); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index b4672c14b49a..0b25c444a8b8 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -27,6 +27,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -100,7 +101,6 @@ public class BubbleOverflowActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bubble_overflow_activity); - setBackgroundColor(); mEmptyState = findViewById(R.id.bubble_overflow_empty_state); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); @@ -141,34 +141,25 @@ public class BubbleOverflowActivity extends Activity { * Handle theme changes. */ void updateTheme() { - final int mode = - getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + Resources res = getResources(); + final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (mode) { - case Configuration.UI_MODE_NIGHT_NO: - if (DEBUG_OVERFLOW) { - Log.d(TAG, "Set overflow UI to light mode"); - } + case Configuration.UI_MODE_NIGHT_YES: mEmptyStateImage.setImageDrawable( - getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + findViewById(android.R.id.content) + .setBackgroundColor(res.getColor(R.color.bubbles_dark)); break; - case Configuration.UI_MODE_NIGHT_YES: - if (DEBUG_OVERFLOW) { - Log.d(TAG, "Set overflow UI to dark mode"); - } + + case Configuration.UI_MODE_NIGHT_NO: mEmptyStateImage.setImageDrawable( - getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + res.getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + findViewById(android.R.id.content) + .setBackgroundColor(res.getColor(R.color.bubbles_light)); break; } } - void setBackgroundColor() { - final TypedArray ta = getApplicationContext().obtainStyledAttributes( - new int[]{android.R.attr.colorBackgroundFloating}); - int bgColor = ta.getColor(0, Color.WHITE); - ta.recycle(); - findViewById(android.R.id.content).setBackgroundColor(bgColor); - } - void onDataChanged(List<Bubble> bubbles) { mOverflowBubbles.clear(); mOverflowBubbles.addAll(bubbles); @@ -300,9 +291,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V }); // If the bubble was persisted, the entry is null but it should have shortcut info - ShortcutInfo info = b.getEntry() == null - ? b.getShortcutInfo() - : b.getEntry().getRanking().getShortcutInfo(); + ShortcutInfo info = b.getShortcutInfo(); if (info == null) { Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index d5fe9b2953de..55425921ddb3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -91,7 +91,6 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -287,7 +286,7 @@ public class BubbleStackView extends FrameLayout private BubbleController.BubbleExpandListener mExpandListener; /** Callback to run when we want to unbubble the given notification's conversation. */ - private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private Consumer<String> mUnbubbleConversationCallback; private SysUiState mSysUiState; @@ -534,6 +533,8 @@ public class BubbleStackView extends FrameLayout mMagneticTarget, mIndividualBubbleMagnetListener); + hideImeFromExpandedBubble(); + // Save the magnetized individual bubble so we can dispatch touch events to it. mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); } else { @@ -997,10 +998,7 @@ public class BubbleStackView extends FrameLayout mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( view -> { showManageMenu(false /* show */); - final Bubble bubble = mBubbleData.getSelectedBubble(); - if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - mUnbubbleConversationCallback.accept(bubble.getEntry()); - } + mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( @@ -1098,13 +1096,17 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow = new BubbleOverflow(getContext()); mBubbleOverflow.setUpOverflow(mBubbleContainer, this); } else { - mBubbleContainer.removeView(mBubbleOverflow.getBtn()); + mBubbleContainer.removeView(mBubbleOverflow.getIconView()); mBubbleOverflow.setUpOverflow(mBubbleContainer, this); overflowBtnIndex = mBubbleContainer.getChildCount(); } - mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, + mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow)); + mBubbleOverflow.getIconView().setOnClickListener(v -> { + setSelectedBubble(mBubbleOverflow); + showManageMenu(false); + }); + updateOverflowVisibility(); } /** * Handle theme changes. @@ -1348,7 +1350,7 @@ public class BubbleStackView extends FrameLayout /** Sets the function to call to un-bubble the given conversation. */ public void setUnbubbleConversationCallback( - Consumer<NotificationEntry> unbubbleConversationCallback) { + Consumer<String> unbubbleConversationCallback) { mUnbubbleConversationCallback = unbubbleConversationCallback; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 525d5b56cc8e..3e4ff5262bbd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Parcelable; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.PathParser; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; /** * Simple task to inflate views & load necessary info to display a bubble. @@ -129,35 +128,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Nullable static BubbleViewInfo populate(Context c, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { - final NotificationEntry entry = b.getEntry(); - if (entry == null) { - // populate from ShortcutInfo when NotificationEntry is not available - final ShortcutInfo s = b.getShortcutInfo(); - return populate(c, stackView, iconFactory, skipInflation || b.isInflated(), - s.getPackage(), s.getUserHandle(), s, null); - } - final StatusBarNotification sbn = entry.getSbn(); - final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId(); - final ShortcutInfo si = bubbleShortcutId == null - ? null : entry.getRanking().getShortcutInfo(); - return populate( - c, stackView, iconFactory, skipInflation || b.isInflated(), - sbn.getPackageName(), sbn.getUser(), si, entry); - } - - private static BubbleViewInfo populate( - @NonNull final Context c, - @NonNull final BubbleStackView stackView, - @NonNull final BubbleIconFactory iconFactory, - final boolean isInflated, - @NonNull final String packageName, - @NonNull final UserHandle user, - @Nullable final ShortcutInfo shortcutInfo, - @Nullable final NotificationEntry entry) { BubbleViewInfo info = new BubbleViewInfo(); // View inflation: only should do this once per bubble - if (!isInflated) { + if (!skipInflation && !b.isInflated()) { LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); @@ -167,8 +141,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.setStackView(stackView); } - if (shortcutInfo != null) { - info.shortcutInfo = shortcutInfo; + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); } // App name & app icon @@ -178,7 +152,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Drawable appIcon; try { appInfo = pm.getApplicationInfo( - packageName, + b.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -186,17 +160,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (appInfo != null) { info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } - appIcon = pm.getApplicationIcon(packageName); - badgedIcon = pm.getUserBadgedIcon(appIcon, user); + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + packageName); + Log.w(TAG, "Unable to find package: " + b.getPackageName()); return null; } // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - entry == null ? null : entry.getBubbleMetadata()); + b.getIcon()); if (bubbleDrawable == null) { // Default to app icon bubbleDrawable = appIcon; @@ -222,8 +196,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Color.WHITE, WHITE_SCRIM_ALPHA); // Flyout - if (entry != null) { - info.flyoutMessage = extractFlyoutMessage(c, entry); + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); } return info; } @@ -235,8 +211,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask * notification, based on its type. Returns null if there should not be an update message. */ @NonNull - static Bubble.FlyoutMessage extractFlyoutMessage(Context context, - NotificationEntry entry) { + static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) { + Objects.requireNonNull(entry); final Notification underlyingNotif = entry.getSbn().getNotification(); final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); @@ -264,20 +240,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (latestMessage != null) { bubbleMessage.message = latestMessage.getText(); Person sender = latestMessage.getSenderPerson(); - bubbleMessage.senderName = sender != null - ? sender.getName() - : null; - + bubbleMessage.senderName = sender != null ? sender.getName() : null; bubbleMessage.senderAvatar = null; - if (sender != null && sender.getIcon() != null) { - if (sender.getIcon().getType() == Icon.TYPE_URI - || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - sender.getIcon().getUri(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context); - } + bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; return bubbleMessage; } } else if (Notification.InboxStyle.class.equals(style)) { @@ -306,4 +271,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask return bubbleMessage; } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 355c4b115c8d..24768cd84a76 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -24,5 +24,6 @@ data class BubbleEntity( val shortcutId: String, val key: String, val desiredHeight: Int, - @DimenRes val desiredHeightResId: Int + @DimenRes val desiredHeightResId: Int, + val title: String? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index a8faf258da07..66fff3386ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -33,6 +33,7 @@ private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" private const val ATTR_DESIRED_HEIGHT = "h" private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" +private const val ATTR_TITLE = "t" /** * Writes the bubbles in xml format into given output stream. @@ -63,6 +64,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_KEY, bubble.key) serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) + bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -92,7 +94,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, parser.getAttributeWithName(ATTR_KEY) ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, - parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_TITLE) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 775a1649702a..b86e1d0503d4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -480,7 +480,17 @@ class MediaHierarchyManager @Inject constructor( if (inOverlay) { rootOverlay!!.add(mediaFrame) } else { + // When adding back to the host, let's make sure to reset the bounds. + // Usually adding the view will trigger a layout that does this automatically, + // but we sometimes suppress this. targetHost.addView(mediaFrame) + val left = targetHost.paddingLeft + val top = targetHost.paddingTop + mediaFrame.setLeftTopRightBottom( + left, + top, + left + currentBounds.width(), + top + currentBounds.height()) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index 40d317c7bb22..dc157a8dd257 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -139,12 +139,7 @@ class DoubleLineTileLayout( } tilesToShow = actualColumns * NUM_LINES - val interTileSpace = if (actualColumns <= 2) { - // Extra "column" of padding to be distributed on each end - (availableWidth - actualColumns * smallTileSize) / actualColumns - } else { - (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1) - } + val spacePerTile = availableWidth / actualColumns for (index in 0 until mRecords.size) { val tileView = mRecords[index].tileView @@ -154,15 +149,16 @@ class DoubleLineTileLayout( tileView.visibility = View.VISIBLE if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView) val column = index % actualColumns - val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2) + val left = getLeftForColumn(column, spacePerTile) val top = if (index < actualColumns) 0 else getTopBottomRow() tileView.layout(left, top, left + smallTileSize, top + smallTileSize) } } } - private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int { - return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace) + private fun getLeftForColumn(column: Int, spacePerTile: Int): Int { + // Distribute the space evenly among all tiles. + return (column * spacePerTile + spacePerTile / 2.0f - smallTileSize / 2.0f).toInt() } private fun getTopBottomRow() = smallTileSize + cellMarginVertical diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java index c204d94916a4..aa17c4aa79b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java @@ -17,6 +17,9 @@ package com.android.systemui.qs; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; import android.widget.ScrollView; /** @@ -24,8 +27,12 @@ import android.widget.ScrollView; */ public class NonInterceptingScrollView extends ScrollView { + private final int mTouchSlop; + private float mDownY; + public NonInterceptingScrollView(Context context, AttributeSet attrs) { super(context, attrs); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override @@ -34,10 +41,52 @@ public class NonInterceptingScrollView extends ScrollView { switch (action) { case MotionEvent.ACTION_DOWN: if (canScrollVertically(1)) { - requestDisallowInterceptTouchEvent(true); + // If we can scroll down, make sure we're not intercepted by the parent + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } } break; } return super.onTouchEvent(ev); } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // If there's a touch on this view and we can scroll down, we don't want to be intercepted + int action = ev.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // If we can scroll down, make sure non of our parents intercepts us. + if (canScrollVertically(1)) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + mDownY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: { + final int y = (int) ev.getY(); + final float yDiff = y - mDownY; + if (yDiff < -mTouchSlop && !canScrollVertically(1)) { + // Don't intercept touches that are overscrolling. + return false; + } + break; + } + } + return super.onInterceptTouchEvent(ev); + } + + public int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop)); + } + return scrollRange; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index aaff9ac47ebf..28369925367a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -66,6 +66,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private int mHorizontalClipBound; private final Rect mClippingRect; private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); + private int mExcessHeight; + private int mLastExcessHeight; + private int mMinRows = 1; + private int mMaxColumns = TileLayout.NO_MAX_COLUMNS; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -195,11 +199,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); mAdapter.notifyDataSetChanged(); } + private TilePage createTilePage() { + TilePage page = (TilePage) LayoutInflater.from(getContext()) + .inflate(R.layout.qs_paged_page, this, false); + page.setMinRows(mMinRows); + page.setMaxColumns(mMaxColumns); + return page; + } + public void setPageIndicator(PageIndicator indicator) { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); @@ -298,8 +309,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } while (mPages.size() < numPages) { if (DEBUG) Log.d(TAG, "Adding page"); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); } while (mPages.size() > numPages) { if (DEBUG) Log.d(TAG, "Removing page"); @@ -342,17 +352,54 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public boolean setMinRows(int minRows) { + mMinRows = minRows; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMinRows(minRows)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMaxColumns(maxColumns)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + /** + * Set the amount of excess space that we gave this view compared to the actual available + * height. This is because this view is in a scrollview. + */ + public void setExcessHeight(int excessHeight) { + mExcessHeight = excessHeight; + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int nTiles = mTiles.size(); // If we have no reason to recalculate the number of rows, skip this step. In particular, // if the height passed by its parent is the same as the last time, we try not to remeasure. - if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) { + if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec) + || mLastExcessHeight != mExcessHeight) { mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec); + mLastExcessHeight = mExcessHeight; // Only change the pages if the number of rows or columns (from updateResources) has // changed or the tiles have changed - if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) { + int availableHeight = mLastMaxHeight - mExcessHeight; + if (mPages.get(0).updateMaxRows(availableHeight, nTiles) || mDistributeTiles) { mDistributeTiles = false; distributeTiles(); } @@ -485,14 +532,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { // up. return Math.max(mColumns * mRows, 1); } - - @Override - public boolean updateResources() { - final int sidePadding = getContext().getResources().getDimensionPixelSize( - R.dimen.notification_side_paddings); - setPadding(sidePadding, 0, sidePadding, 0); - return super.updateResources(); - } } private final PagerAdapter mAdapter = new PagerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index ce002297e1a1..bc8f5a8fb652 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -18,6 +18,7 @@ import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; +import android.widget.ScrollView; import com.android.systemui.Dependency; import com.android.systemui.plugins.qs.QS; @@ -30,7 +31,6 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.Collection; @@ -66,6 +66,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private TouchAnimator mNonfirstPageAnimator; private TouchAnimator mNonfirstPageDelayedAnimator; private TouchAnimator mBrightnessAnimator; + private boolean mNeedsAnimatorUpdate = false; private boolean mOnKeyguard; @@ -98,6 +99,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); } + + public void onQsScrollingChanged() { + // Lazily update animators whenever the scrolling changes + mNeedsAnimatorUpdate = true; + } + public void setOnKeyguard(boolean onKeyguard) { mOnKeyguard = onKeyguard; updateQQSVisibility(); @@ -172,6 +179,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } private void updateAnimators() { + mNeedsAnimatorUpdate = false; TouchAnimator.Builder firstPageBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); TouchAnimator.Builder translationYBuilder = new Builder(); @@ -286,13 +294,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .setListener(this) .build(); // Fade in the tiles/labels as we reach the final position. - mFirstPageDelayedAnimator = new TouchAnimator.Builder() + Builder builder = new Builder() .setStartDelay(EXPANDED_TILE_DELAY) - .addFloat(tileLayout, "alpha", 0, 1) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) - .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build(); - mAllViews.add(mQsPanel.getDivider()); - mAllViews.add(mQsPanel.getFooter().getView()); + .addFloat(tileLayout, "alpha", 0, 1); + if (mQsPanel.getSecurityFooter() != null) { + builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1); + } + mFirstPageDelayedAnimator = builder.build(); + if (mQsPanel.getSecurityFooter() != null) { + mAllViews.add(mQsPanel.getSecurityFooter().getView()); + } float px = 0; float py = 1; if (tiles.size() <= 3) { @@ -308,7 +319,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } mNonfirstPageAnimator = new TouchAnimator.Builder() .addFloat(mQuickQsPanel, "alpha", 1, 0) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) .setListener(mNonFirstPageListener) .setEndDelay(.5f) .build(); @@ -339,10 +349,18 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } + if (!(view instanceof PagedTileLayout)) { + // Remove the scrolling position of all scroll views other than the viewpager + loc1[0] -= view.getScrollX(); + loc1[1] -= view.getScrollY(); + } getRelativePositionInt(loc1, (View) view.getParent(), parent); } public void setPosition(float position) { + if (mNeedsAnimatorUpdate) { + updateAnimators(); + } if (mFirstPageAnimator == null) return; if (mOnKeyguard) { if (mShowCollapsedOnKeyguard) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 1c3b6850afc1..0332bc3e0618 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -43,6 +43,7 @@ public class QSContainerImpl extends FrameLayout { private float mQsExpansion; private QSCustomizer mQSCustomizer; private View mDragHandle; + private View mQSPanelContainer; private View mBackground; private View mBackgroundGradient; @@ -61,6 +62,7 @@ public class QSContainerImpl extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mQSPanel = findViewById(R.id.quick_settings_panel); + mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view); mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); @@ -95,7 +97,7 @@ public class QSContainerImpl extends FrameLayout { Configuration config = getResources().getConfiguration(); boolean navBelow = config.smallestScreenWidthDp >= 600 || config.orientation != Configuration.ORIENTATION_LANDSCAPE; - MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanel.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); // The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to // subtract its height. We do not care if the collapsed notifications fit in the screen. @@ -109,12 +111,11 @@ public class QSContainerImpl extends FrameLayout { + layoutParams.rightMargin; final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, layoutParams.width); - // Measure with EXACTLY. That way, PagedTileLayout will only use excess height and will be - // measured last, after other views and padding is accounted for. - mQSPanel.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY)); - int width = mQSPanel.getMeasuredWidth() + padding; + mQSPanelContainer.measure(qsPanelWidthSpec, + MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); + int width = mQSPanelContainer.getMeasuredWidth() + padding; int height = layoutParams.topMargin + layoutParams.bottomMargin - + mQSPanel.getMeasuredHeight() + getPaddingBottom(); + + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom(); super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after @@ -130,7 +131,7 @@ public class QSContainerImpl extends FrameLayout { // Do not measure QSPanel again when doing super.onMeasure. // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect) // size to the one used for determining the number of rows and then the number of pages. - if (child != mQSPanel) { + if (child != mQSPanelContainer) { super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); } @@ -151,10 +152,10 @@ public class QSContainerImpl extends FrameLayout { } private void updateResources() { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams(); layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - mQSPanel.setLayoutParams(layoutParams); + mQSPanelContainer.setLayoutParams(layoutParams); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); mContentPaddingStart = getResources().getDimensionPixelSize( @@ -185,7 +186,7 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin the drag handle to the bottom of the panel. mDragHandle.setTranslationY(height - mDragHandle.getHeight()); - mBackground.setTop(mQSPanel.getTop()); + mBackground.setTop(mQSPanelContainer.getTop()); mBackground.setBottom(height); } @@ -223,7 +224,7 @@ public class QSContainerImpl extends FrameLayout { LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.rightMargin = mSideMargins; lp.leftMargin = mSideMargins; - if (view == mQSPanel) { + if (view == mQSPanelContainer) { // QS panel lays out some of its content full width mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd); } else if (view == mHeader) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 6af9e1ef4571..f1bb8996e181 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -72,6 +71,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca protected QuickStatusBarHeader mHeader; private QSCustomizer mQSCustomizer; protected QSPanel mQSPanel; + protected NonInterceptingScrollView mQSPanelScrollView; private QSDetail mQSDetail; private boolean mListening; private QSContainerImpl mContainer; @@ -122,8 +122,20 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mQSPanel = view.findViewById(R.id.quick_settings_panel); + mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); + mQSPanelScrollView.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + updateQsBounds(); + }); + mQSPanelScrollView.setOnScrollChangeListener( + (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + // Lazily update animators whenever the scrolling changes + mQSAnimator.onQsScrollingChanged(); + mHeader.setExpandedScrollAmount(scrollY); + }); mQSDetail = view.findViewById(R.id.qs_detail); mHeader = view.findViewById(R.id.header); + mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); @@ -133,8 +145,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter); - mQSAnimator = new QSAnimator(this, - mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSCustomizer = view.findViewById(R.id.qs_customize); mQSCustomizer.setQs(this); @@ -319,11 +331,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); - } - - @Override public void setHeaderClickable(boolean clickable) { if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); } @@ -394,7 +401,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mLastViewHeight = currentHeight; boolean fullyExpanded = expansion == 1; - int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom(); + boolean fullyCollapsed = expansion == 0.0f; + int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); float panelTranslationY = translationScaleY * heightDiff; // Let the views animate their contents correctly by giving them the necessary context. @@ -403,19 +412,19 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mQSPanel.getQsTileRevealController().setExpansion(expansion); mQSPanel.getTileLayout().setExpansion(expansion); - mQSPanel.setTranslationY(translationScaleY * heightDiff); + mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); + if (fullyCollapsed) { + mQSPanelScrollView.setScrollY(0); + } mQSDetail.setFullyExpanded(fullyExpanded); - if (fullyExpanded) { - // Always draw within the bounds of the view when fully expanded. - mQSPanel.setClipBounds(null); - } else { + if (!fullyExpanded) { // Set bounds on the QS panel so it doesn't run over the header when animating. - mQsBounds.top = (int) -mQSPanel.getTranslationY(); - mQsBounds.right = mQSPanel.getWidth(); - mQsBounds.bottom = mQSPanel.getHeight(); - mQSPanel.setClipBounds(mQsBounds); + mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); + mQsBounds.right = mQSPanelScrollView.getWidth(); + mQsBounds.bottom = mQSPanelScrollView.getHeight(); } + updateQsBounds(); if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); @@ -423,31 +432,63 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca updateMediaPositions(); } + private void updateQsBounds() { + if (mLastQSExpansion == 1.0f) { + // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because + // it's a scrollview and otherwise wouldn't be clipped. + mQsBounds.set(0, 0, mQSPanelScrollView.getWidth(), mQSPanelScrollView.getHeight()); + } + mQSPanelScrollView.setClipBounds(mQsBounds); + } + private void updateMediaPositions() { if (Utils.useQsMediaPlayer(getContext())) { mContainer.getLocationOnScreen(mTmpLocation); float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight(); - pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost()); - pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(), - mHeader.getHeaderQsPanel().getMediaHost()); + // The Media can be scrolled off screen by default, let's offset it + float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY() + + mQSPanelScrollView.getScrollRange(); + // The expanded media host should never move below the laid out position + pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */); + // The expanded media host should never move above the laid out position + pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(), + false /* expanded */); } } - private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) { + private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); if (mLastQSExpansion > 0) { - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) hostView.getLayoutParams(); - float targetPosition = absoluteBottomPosition - params.bottomMargin + float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top - hostView.getTranslationY(); - hostView.setTranslationY(targetPosition - currentPosition); + float translationY = targetPosition - currentPosition; + if (expanded) { + // Never go below the laid out position. This is necessary since the qs panel can + // change in height and we don't want to ever go below it's position + translationY = Math.min(translationY, 0); + } else { + translationY = Math.max(translationY, 0); + } + hostView.setTranslationY(translationY); } else { hostView.setTranslationY(0); } } + private float getTotalBottomMargin(View startView) { + int result = 0; + View child = startView; + View parent = (View) startView.getParent(); + while (!(parent instanceof QSContainerImpl) && parent != null) { + result += parent.getHeight() - child.getBottom(); + child = parent; + parent = (View) parent.getParent(); + } + return result; + } + private boolean headerWillBeAnimating() { return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardShowing(); @@ -504,7 +545,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); - mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); + mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE + : View.INVISIBLE); mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. @@ -521,9 +563,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return getView().getHeight(); } if (mQSDetail.isClosingDetail()) { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams(); int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + - + mQSPanel.getMeasuredHeight(); + + mQSPanelScrollView.getMeasuredHeight(); return panelHeight + getView().getPaddingBottom(); } else { return getView().getMeasuredHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 78448785fe2f..ecdb2c91ca48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -16,10 +16,10 @@ package com.android.systemui.qs; -import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import static com.android.systemui.util.Utils.useQsMediaPlayer; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -29,8 +29,8 @@ import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.service.quicksettings.Tile; import android.util.AttributeSet; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,7 +39,7 @@ import android.widget.LinearLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.Utils; +import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -83,38 +83,65 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private final BroadcastDispatcher mBroadcastDispatcher; protected final MediaHost mMediaHost; + + /** + * The index where the content starts that needs to be moved between parents + */ + private final int mMovableContentStartIndex; private String mCachedSpecs = ""; - protected final View mBrightnessView; + + @Nullable + protected View mBrightnessView; + @Nullable + private BrightnessController mBrightnessController; + private final H mHandler = new H(); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); - private final QSTileRevealController mQsTileRevealController; + private QSTileRevealController mQsTileRevealController; /** Whether or not the QS media player feature is enabled. */ protected boolean mUsingMediaPlayer; + private int mVisualMarginStart; + private int mVisualMarginEnd; protected boolean mExpanded; protected boolean mListening; private QSDetail.Callback mCallback; - private BrightnessController mBrightnessController; private final DumpManager mDumpManager; private final QSLogger mQSLogger; protected final UiEventLogger mUiEventLogger; protected QSTileHost mHost; - protected QSSecurityFooter mFooter; + @Nullable + protected QSSecurityFooter mSecurityFooter; + + @Nullable + protected View mFooter; + + @Nullable + private ViewGroup mHeaderContainer; private PageIndicator mFooterPageIndicator; private boolean mGridContentVisible = true; private int mContentMarginStart; private int mContentMarginEnd; private int mVisualTilePadding; - - protected QSTileLayout mTileLayout; + private boolean mUsingHorizontalLayout; private QSCustomizer mCustomizePanel; private Record mDetailRecord; private BrightnessMirrorController mBrightnessMirrorController; - private View mDivider; + private LinearLayout mHorizontalLinearLayout; + private LinearLayout mHorizontalContentContainer; + + // Only used with media + private QSTileLayout mHorizontalTileLayout; + protected QSTileLayout mRegularTileLayout; + protected QSTileLayout mTileLayout; + private int mLastOrientation = -1; + private int mMediaTotalBottomMargin; + private int mFooterMarginStartHorizontal; + @Inject public QSPanel( @@ -128,7 +155,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne ) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); + mMediaTotalBottomMargin = getResources().getDimensionPixelSize( + R.dimen.quick_settings_bottom_margin_media); mMediaHost = mediaHost; + mMediaHost.setVisibleChangedListener((visible) -> { + switchTileLayout(); + return null; + }); mContext = context; mQSLogger = qsLogger; mDumpManager = dumpManager; @@ -137,71 +170,97 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setOrientation(VERTICAL); - mBrightnessView = LayoutInflater.from(mContext).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - - mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( - R.layout.qs_paged_tile_layout, this, false); + addViewsAboveTiles(); + mMovableContentStartIndex = getChildCount(); + mRegularTileLayout = createRegularTileLayout(); + + if (mUsingMediaPlayer) { + mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext); + mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + mHorizontalLinearLayout.setClipChildren(false); + mHorizontalLinearLayout.setClipToPadding(false); + + mHorizontalContentContainer = new RemeasuringLinearLayout(mContext); + mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL); + mHorizontalContentContainer.setClipChildren(false); + mHorizontalContentContainer.setClipToPadding(false); + + mHorizontalTileLayout = createHorizontalTileLayout(); + LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); + int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); + lp.setMarginStart(0); + lp.setMarginEnd(marginSize); + lp.gravity = Gravity.CENTER_VERTICAL; + mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp); + + lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1); + addView(mHorizontalLinearLayout, lp); + + initMediaHostState(); + } + addSecurityFooter(); + if (mRegularTileLayout instanceof PagedTileLayout) { + mQsTileRevealController = new QSTileRevealController(mContext, this, + (PagedTileLayout) mRegularTileLayout); + } mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs); - mTileLayout.setListening(mListening); - addView((View) mTileLayout); - - mQsTileRevealController = new QSTileRevealController(mContext, this, - (PagedTileLayout) mTileLayout); - - addDivider(); - - mFooter = new QSSecurityFooter(this, context); - addView(mFooter.getView()); - updateResources(); + } + protected void addSecurityFooter() { + mSecurityFooter = new QSSecurityFooter(this, mContext); + } + + protected void addViewsAboveTiles() { + mBrightnessView = LayoutInflater.from(mContext).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + addView(mBrightnessView); mBrightnessController = new BrightnessController(getContext(), findViewById(R.id.brightness_slider), mBroadcastDispatcher); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - // Add media carousel at the end - if (useQsMediaPlayer(getContext())) { - addMediaHostView(); + protected QSTileLayout createRegularTileLayout() { + if (mRegularTileLayout == null) { + mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( + R.layout.qs_paged_tile_layout, this, false); } + return mRegularTileLayout; + } + + + protected QSTileLayout createHorizontalTileLayout() { + return createRegularTileLayout(); } - protected void addMediaHostView() { + protected void initMediaHostState() { mMediaHost.setExpansion(1.0f); mMediaHost.setShowsOnlyActiveMedia(false); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); - ViewGroup hostView = mMediaHost.getHostView(); - addView(hostView); - int bottomPadding = getResources().getDimensionPixelSize( - R.dimen.quick_settings_expanded_bottom_margin); - MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.bottomMargin = bottomPadding; - hostView.setLayoutParams(layoutParams); - updateMediaHostContentMargins(); - } - - protected void addDivider() { - mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false); - mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(), - getColorForState(mContext, Tile.STATE_ACTIVE))); - addView(mDivider); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + if (mTileLayout instanceof PagedTileLayout) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space // not used by the other children to PagedTileLayout. However, in this case, LinearLayout // assumes that PagedTileLayout would use all the excess space. This is not the case as // PagedTileLayout height is quantized (because it shows a certain number of rows). // Therefore, after everything is measured, we need to make sure that we add up the correct // total height - super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = getPaddingBottom() + getPaddingTop(); int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { @@ -215,10 +274,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setMeasuredDimension(getMeasuredWidth(), height); } - public View getDivider() { - return mDivider; - } - public QSTileRevealController getQsTileRevealController() { return mQsTileRevealController; } @@ -273,7 +328,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override public void onTuningChanged(String key, String newValue) { - if (QS_SHOW_BRIGHTNESS.equals(key)) { + if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) { updateViewVisibilityForTuningValue(mBrightnessView, newValue); } } @@ -316,6 +371,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateBrightnessMirror(); } + @Nullable View getBrightnessView() { return mBrightnessView; } @@ -328,7 +384,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mHost = host; mHost.addCallback(this); setTiles(mHost.getTiles()); - mFooter.setHostEnvironment(host); + if (mSecurityFooter != null) { + mSecurityFooter.setHostEnvironment(host); + } mCustomizePanel = customizer; if (mCustomizePanel != null) { mCustomizePanel.setHost(mHost); @@ -341,18 +399,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param pageIndicator indicator to use for page scrolling */ public void setFooterPageIndicator(PageIndicator pageIndicator) { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { mFooterPageIndicator = pageIndicator; updatePageIndicator(); } } private void updatePageIndicator() { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { if (mFooterPageIndicator != null) { mFooterPageIndicator.setVisibility(View.GONE); - ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator); + ((PagedTileLayout) mRegularTileLayout).setPageIndicator(mFooterPageIndicator); } } } @@ -364,6 +422,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void updateResources() { int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); + mFooterMarginStartHorizontal = getResources().getDimensionPixelSize( + R.dimen.qs_footer_horizontal_margin); mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f); updatePadding(); @@ -379,8 +439,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected void updatePadding() { final Resources res = mContext.getResources(); + int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); + if (mUsingHorizontalLayout) { + // When using the horizontal layout, our space is quite constrained. We therefore + // reduce some of the padding on the top, which makes the brightness bar overlapp, + // but since that has naturally quite a bit of built in padding, that's fine. + padding = (int) (padding * 0.6f); + } setPaddingRelative(getPaddingStart(), - res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), + padding, getPaddingEnd(), res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); } @@ -388,10 +455,165 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mFooter.onConfigurationChanged(); + if (mSecurityFooter != null) { + mSecurityFooter.onConfigurationChanged(); + } updateResources(); updateBrightnessMirror(); + + if (newConfig.orientation != mLastOrientation) { + mLastOrientation = newConfig.orientation; + switchTileLayout(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mFooter = findViewById(R.id.qs_footer); + switchTileLayout(true /* force */); + } + + boolean switchTileLayout() { + return switchTileLayout(false /* force */); + } + + private boolean switchTileLayout(boolean force) { + /** Whether or not the QuickQSPanel currently contains a media player. */ + boolean horizontal = shouldUseHorizontalLayout(); + if (horizontal != mUsingHorizontalLayout || force) { + mUsingHorizontalLayout = horizontal; + View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout; + View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout; + ViewGroup newParent = horizontal ? mHorizontalContentContainer : this; + QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout; + if (hiddenView != null && + (mRegularTileLayout != mHorizontalTileLayout || + hiddenView != mRegularTileLayout)) { + // Only hide the view if the horizontal and the regular view are different, + // otherwise its reattached. + hiddenView.setVisibility(View.GONE); + } + visibleView.setVisibility(View.VISIBLE); + switchAllContentToParent(newParent, newLayout); + reAttachMediaHost(); + if (mTileLayout != null) { + mTileLayout.setListening(false); + for (TileRecord record : mRecords) { + mTileLayout.removeTile(record); + record.tile.removeCallback(record.callback); + } + } + mTileLayout = newLayout; + if (mHost != null) setTiles(mHost.getTiles()); + newLayout.setListening(mListening); + if (needsDynamicRowsAndColumns()) { + newLayout.setMinRows(horizontal ? 2 : 1); + // Let's use 3 columns to match the current layout + newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS); + } + updateTileLayoutMargins(); + updateFooterMargin(); + updateMediaHostContentMargins(); + updateHorizontalLinearLayoutMargins(); + updatePadding(); + return true; + } + return false; + } + + private void updateHorizontalLinearLayoutMargins() { + if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) { + LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams(); + lp.bottomMargin = mMediaTotalBottomMargin - getPaddingBottom(); + mHorizontalLinearLayout.setLayoutParams(lp); + } + } + + /** + * @return true if the margin bottom of the media view should be on the media host or false + * if they should be on the HorizontalLinearLayout. Returning {@code false} is useful + * to visually center the tiles in the Media view, which doesn't work when the + * expanded panel actually scrolls. + */ + protected boolean displayMediaMarginsOnMedia() { + return true; + } + + protected boolean needsDynamicRowsAndColumns() { + return true; + } + + private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) { + int index = parent == this ? mMovableContentStartIndex : 0; + + // Let's first move the tileLayout to the new parent, since that should come first. + switchToParent((View) newLayout, parent, index); + index++; + + if (mSecurityFooter != null) { + View view = mSecurityFooter.getView(); + LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + if (mUsingHorizontalLayout && mHeaderContainer != null) { + // Adding the security view to the header, that enables us to avoid scrolling + layoutParams.width = 0; + layoutParams.weight = 1.6f; + switchToParent(view, mHeaderContainer, 1 /* always in second place */); + } else { + layoutParams.width = LayoutParams.WRAP_CONTENT; + layoutParams.weight = 0; + switchToParent(view, parent, index); + index++; + } + view.setLayoutParams(layoutParams); + } + + if (mFooter != null) { + // Then the footer with the settings + switchToParent(mFooter, parent, index); + } + } + + private void switchToParent(View child, ViewGroup parent, int index) { + ViewGroup currentParent = (ViewGroup) child.getParent(); + if (currentParent != parent || currentParent.indexOfChild(child) != index) { + if (currentParent != null) { + currentParent.removeView(child); + } + parent.addView(child, index); + } + } + + private boolean shouldUseHorizontalLayout() { + return mUsingMediaPlayer && mMediaHost.getVisible() + && getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } + + protected void reAttachMediaHost() { + if (!mUsingMediaPlayer) { + return; + } + boolean horizontal = shouldUseHorizontalLayout(); + ViewGroup host = mMediaHost.getHostView(); + ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; + ViewGroup currentParent = (ViewGroup) host.getParent(); + if (currentParent != newParent) { + if (currentParent != null) { + currentParent.removeView(host); + } + newParent.addView(host); + LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.weight = horizontal ? 1.2f : 0; + // Add any bottom margin, such that the total spacing is correct. This is only + // necessary if the view isn't horizontal, since otherwise the padding is + // carried in the parent of this view (to ensure correct vertical alignment) + layoutParams.bottomMargin = !horizontal || displayMediaMarginsOnMedia() + ? mMediaTotalBottomMargin - getPaddingBottom() : 0; + } } public void updateBrightnessMirror() { @@ -457,13 +679,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void setListening(boolean listening, boolean expanded) { setListening(listening && expanded); - getFooter().setListening(listening); + if (mSecurityFooter != null) { + mSecurityFooter.setListening(listening); + } // Set the listening as soon as the QS fragment starts listening regardless of the expansion, // so it will update the current brightness before the slider is visible. setBrightnessListening(listening); } public void setBrightnessListening(boolean listening) { + if (mBrightnessController == null) { + return; + } if (listening) { mBrightnessController.registerCallbacks(); } else { @@ -472,11 +699,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public void refreshAllTiles() { - mBrightnessController.checkRestrictionAndSetEnabled(); + if (mBrightnessController != null) { + mBrightnessController.checkRestrictionAndSetEnabled(); + } for (TileRecord r : mRecords) { r.tile.refreshState(); } - mFooter.refreshState(); + if (mSecurityFooter != null) { + mSecurityFooter.refreshState(); + } } public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) { @@ -728,12 +959,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return null; } - public QSSecurityFooter getFooter() { - return mFooter; + @Nullable + public QSSecurityFooter getSecurityFooter() { + return mSecurityFooter; } public void showDeviceMonitoringDialog() { - mFooter.showDeviceMonitoringDialog(); + if (mSecurityFooter != null) { + mSecurityFooter.showDeviceMonitoringDialog(); + } } public void setContentMargins(int startMargin, int endMargin) { @@ -744,6 +978,24 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding, mContentMarginEnd - mVisualTilePadding); updateMediaHostContentMargins(); + updateFooterMargin(); + } + + private void updateFooterMargin() { + if (mFooter != null) { + int footerMargin = 0; + int indicatorMargin = 0; + if (mUsingHorizontalLayout) { + footerMargin = mFooterMarginStartHorizontal; + indicatorMargin = footerMargin - mVisualMarginEnd; + } + updateMargins(mFooter, footerMargin, 0); + // The page indicator isn't centered anymore because of the visual positioning. + // Let's fix it by adding some margin + if (mFooterPageIndicator != null) { + updateMargins(mFooterPageIndicator, 0, indicatorMargin); + } + } } /** @@ -754,16 +1006,30 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets * to the tile. This can be set on a tileLayout */ - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); + private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { + mVisualMarginStart = visualMarginStart; + mVisualMarginEnd = visualMarginEnd; + updateTileLayoutMargins(); + } + + private void updateTileLayoutMargins() { + int marginEnd = mVisualMarginEnd; + if (mUsingHorizontalLayout) { + marginEnd = 0; + } + updateMargins((View) mTileLayout, mVisualMarginStart, marginEnd); } /** * Update the margins of the media hosts */ protected void updateMediaHostContentMargins() { - if (mUsingMediaPlayer && mMediaHost != null) { - updateMargins(mMediaHost.getHostView(), mContentMarginStart, mContentMarginEnd); + if (mUsingMediaPlayer) { + int marginStart = mContentMarginStart; + if (mUsingHorizontalLayout) { + marginStart = 0; + } + updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd); } } @@ -785,6 +1051,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mMediaHost; } + /** + * Set the header container of quick settings. + */ + public void setHeaderContainer(@NonNull ViewGroup headerContainer) { + mHeaderContainer = headerContainer; + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; @@ -812,6 +1085,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } + protected static class Record { DetailAdapter detailAdapter; int x; @@ -841,6 +1115,26 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne void setListening(boolean listening); + /** + * Set the minimum number of rows to show + * + * @param minRows the minimum. + */ + default boolean setMinRows(int minRows) { + return false; + } + + /** + * Set the max number of collums to show + * + * @param maxColumns the maximum + * + * @return true if the number of visible columns has changed. + */ + default boolean setMaxColumns(int maxColumns) { + return false; + } + default void setExpansion(float expansion) {} int getNumVisibleTiles(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 476af20b78f4..7bcaa7263cc4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.policy.SecurityController; public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { protected static final String TAG = "QSSecurityFooter"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_FORCE_VISIBLE = false; private final View mRootView; private final TextView mFooterText; @@ -60,7 +61,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private final SecurityController mSecurityController; private final ActivityStarter mActivityStarter; private final Handler mMainHandler; - private final View mDivider; private final UserManager mUm; @@ -85,7 +85,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic mActivityStarter = Dependency.get(ActivityStarter.class); mSecurityController = Dependency.get(SecurityController.class); mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); - mDivider = qsPanel == null ? null : qsPanel.getDivider(); mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } @@ -177,7 +176,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileName) { - if (isDeviceManaged) { + if (isDeviceManaged || DEBUG_FORCE_VISIBLE) { if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) { if (organizationName == null) { return mContext.getString( @@ -451,8 +450,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic if (mFooterTextContent != null) { mFooterText.setText(mFooterTextContent); } - mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); - if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE); + mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 94b4cee92965..affb7b91b6a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -24,7 +24,6 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.logging.UiEventLogger; @@ -61,15 +60,7 @@ public class QuickQSPanel extends QSPanel { private boolean mDisabledByPolicy; private int mMaxTiles; protected QSPanel mFullPanel; - /** Whether or not the QuickQSPanel currently contains a media player. */ - private boolean mShowHorizontalTileLayout; - private LinearLayout mHorizontalLinearLayout; - // Only used with media - private QSTileLayout mHorizontalTileLayout; - private QSTileLayout mRegularTileLayout; - private int mLastOrientation = -1; - private int mMediaBottomMargin; @Inject public QuickQSPanel( @@ -82,59 +73,8 @@ public class QuickQSPanel extends QSPanel { UiEventLogger uiEventLogger ) { super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); - if (mFooter != null) { - removeView(mFooter.getView()); - } - if (mTileLayout != null) { - for (int i = 0; i < mRecords.size(); i++) { - mTileLayout.removeTile(mRecords.get(i)); - } - removeView((View) mTileLayout); - } - mMediaBottomMargin = getResources().getDimensionPixelSize( - R.dimen.quick_settings_media_extra_bottom_margin); - if (mUsingMediaPlayer) { - mHorizontalLinearLayout = new LinearLayout(mContext); - mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); - mHorizontalLinearLayout.setClipChildren(false); - mHorizontalLinearLayout.setClipToPadding(false); - - DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context, - mUiEventLogger); - horizontalTileLayout.setPaddingRelative( - horizontalTileLayout.getPaddingStart(), - horizontalTileLayout.getPaddingTop(), - horizontalTileLayout.getPaddingEnd(), - mContext.getResources().getDimensionPixelSize( - R.dimen.qqs_horizonal_tile_padding_bottom)); - mHorizontalTileLayout = horizontalTileLayout; - mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger); - LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); - int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); - lp.setMarginStart(0); - lp.setMarginEnd(marginSize); - lp.gravity = Gravity.CENTER_VERTICAL; - mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp); - - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - - boolean useHorizontal = shouldUseHorizontalTileLayout(); - mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout; - mTileLayout.setListening(mListening); - addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */); - ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE); - mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE); - addView((View) mRegularTileLayout, 0); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mRegularTileLayout); - } else { - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - mTileLayout = new HeaderTileLayout(context, mUiEventLogger); - mTileLayout.setListening(mListening); - addView((View) mTileLayout, 0 /* Between brightness and footer */); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mTileLayout); - } + sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); + applyBottomMargin((View) mRegularTileLayout); } private void applyBottomMargin(View view) { @@ -144,57 +84,47 @@ public class QuickQSPanel extends QSPanel { view.setLayoutParams(layoutParams); } - private void reAttachMediaHost() { - if (mMediaHost == null) { - return; - } - boolean horizontal = shouldUseHorizontalTileLayout(); - ViewGroup host = mMediaHost.getHostView(); - ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; - ViewGroup currentParent = (ViewGroup) host.getParent(); - if (currentParent != newParent) { - if (currentParent != null) { - currentParent.removeView(host); - } - newParent.addView(host); - LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.weight = horizontal ? 1.5f : 0; - layoutParams.bottomMargin = mMediaBottomMargin; - } + @Override + protected void addSecurityFooter() { + // No footer needed + } + + @Override + protected void addViewsAboveTiles() { + // Nothing to add above the tiles + } + + @Override + protected TileLayout createRegularTileLayout() { + return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger); } @Override - protected void addMediaHostView() { - mMediaHost.setVisibleChangedListener((visible) -> { - switchTileLayout(); - return null; - }); + protected QSTileLayout createHorizontalTileLayout() { + return new DoubleLineTileLayout(mContext, mUiEventLogger); + } + + @Override + protected void initMediaHostState() { mMediaHost.setExpansion(0.0f); mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); - reAttachMediaHost(); - updateMediaHostContentMargins(); } @Override - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - if (mUsingMediaPlayer) { - updateMargins((View) mRegularTileLayout, visualMarginStart, visualMarginEnd); - updateMargins((View) mHorizontalTileLayout, visualMarginStart, 0); - } else { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); - } + protected boolean needsDynamicRowsAndColumns() { + return false; // QQS always have the same layout } @Override - protected void updatePadding() { - // QS Panel is setting a top padding by default, which we don't need. + protected boolean displayMediaMarginsOnMedia() { + // Margins should be on the container to visually center the view + return false; } @Override - protected void addDivider() { + protected void updatePadding() { + // QS Panel is setting a top padding by default, which we don't need. } @Override @@ -237,60 +167,6 @@ public class QuickQSPanel extends QSPanel { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (newConfig.orientation != mLastOrientation) { - mLastOrientation = newConfig.orientation; - switchTileLayout(); - } - } - - boolean switchTileLayout() { - if (!mUsingMediaPlayer) return false; - mShowHorizontalTileLayout = shouldUseHorizontalTileLayout(); - if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) { - mHorizontalLinearLayout.setVisibility(View.VISIBLE); - ((View) mRegularTileLayout).setVisibility(View.GONE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mHorizontalTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } else if (!mShowHorizontalTileLayout - && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) { - mHorizontalLinearLayout.setVisibility(View.GONE); - ((View) mRegularTileLayout).setVisibility(View.VISIBLE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mRegularTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } - return false; - } - - private boolean shouldUseHorizontalTileLayout() { - return mMediaHost.getVisible() - && getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } - - /** Returns true if this panel currently uses a horizontal tile layout. */ - public boolean usesHorizontalLayout() { - return mShowHorizontalTileLayout; - } - - @Override public void setHost(QSTileHost host, QSCustomizer customizer) { super.setHost(host, customizer); setTiles(mHost.getTiles()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 20e47b2f2fa9..311eda2f4ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -36,13 +36,13 @@ import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -55,6 +55,7 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; @@ -149,6 +150,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; + private float mExpandedHeaderAlpha = 1.0f; + private float mKeyguardExpansionFraction; @Inject public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -356,7 +359,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements private void updateHeaderTextContainerAlphaAnimator() { mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder() - .addFloat(mHeaderTextContainerView, "alpha", 0, 0, 1) + .addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha) .build(); } @@ -403,6 +406,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements updateResources(); } } + mKeyguardExpansionFraction = keyguardExpansionFraction; } public void disable(int state1, int state2, boolean animate) { @@ -596,4 +600,22 @@ public class QuickStatusBarHeader extends RelativeLayout implements } updateClockPadding(); } + + public void setExpandedScrollAmount(int scrollY) { + // The scrolling of the expanded qs has changed. Since the header text isn't part of it, + // but would overlap content, we're fading it out. + float newAlpha = 1.0f; + if (mHeaderTextContainerView.getHeight() > 0) { + newAlpha = MathUtils.map(0, mHeaderTextContainerView.getHeight() / 2.0f, 1.0f, 0.0f, + scrollY); + newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha); + } + mHeaderTextContainerView.setScrollY(scrollY); + if (newAlpha != mExpandedHeaderAlpha) { + mExpandedHeaderAlpha = newAlpha; + mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha, + mKeyguardExpansionFraction)); + updateHeaderTextContainerAlphaAnimator(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 383c29d90a22..694492a33524 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -17,6 +17,7 @@ import java.util.ArrayList; public class TileLayout extends ViewGroup implements QSTileLayout { + public static final int NO_MAX_COLUMNS = 100; private static final float TILE_ASPECT = 1.2f; private static final String TAG = "TileLayout"; @@ -36,6 +37,9 @@ public class TileLayout extends ViewGroup implements QSTileLayout { // Prototyping with less rows private final boolean mLessRows; + private int mMinRows = 1; + private int mMaxColumns = NO_MAX_COLUMNS; + private int mResourceColumns; public TileLayout(Context context) { this(context, null); @@ -64,6 +68,22 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } } + @Override + public boolean setMinRows(int minRows) { + if (mMinRows != minRows) { + mMinRows = minRows; + updateResources(); + return true; + } + return false; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + return updateColumns(); + } + public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); @@ -91,21 +111,26 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); - final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); - if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1); - if (mColumns != columns) { - mColumns = columns; + if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1); + if (updateColumns()) { requestLayout(); return true; } return false; } + private boolean updateColumns() { + int oldColumns = mColumns; + mColumns = Math.min(mResourceColumns, mMaxColumns); + return oldColumns != mColumns; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED @@ -142,18 +167,19 @@ public class TileLayout extends ViewGroup implements QSTileLayout { * Determines the maximum number of rows that can be shown based on height. Clips at a minimum * of 1 and a maximum of mMaxAllowedRows. * - * @param heightMeasureSpec Available height. + * @param allowedHeight The height this view has visually available * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows. */ - public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) { - final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop + public boolean updateMaxRows(int allowedHeight, int tilesCount) { + final int availableHeight = allowedHeight - mCellMarginTop + // Add the cell margin in order to divide easily by the height + the margin below + mCellMarginVertical; final int previousRows = mRows; mRows = availableHeight / (mCellHeight + mCellMarginVertical); - if (mRows >= mMaxAllowedRows) { + if (mRows < mMinRows) { + mRows = mMinRows; + } else if (mRows >= mMaxAllowedRows) { mRows = mMaxAllowedRows; - } else if (mRows <= 1) { - mRows = 1; } if (mRows > (tilesCount + mColumns - 1) / mColumns) { mRows = (tilesCount + mColumns - 1) / mColumns; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 08be4f872415..011ad19b41db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -255,23 +255,11 @@ class IconManager @Inject constructor( @Throws(InflationException::class) private fun createPeopleAvatar(entry: NotificationEntry): Icon? { - // Attempt to extract form shortcut. - val conversationId = entry.ranking.channel.conversationId - val query = LauncherApps.ShortcutQuery() - .setPackage(entry.sbn.packageName) - .setQueryFlags( - LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC - or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED) - .setShortcutIds(listOf(conversationId)) - val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user) var ic: Icon? = null - if (shortcuts != null && shortcuts.isNotEmpty()) { - ic = shortcuts[0].icon - } - // Fall back to notification large icon if available - if (ic == null) { - ic = entry.sbn.notification.getLargeIcon() + val shortcut = entry.ranking.shortcutInfo + if (shortcut != null) { + ic = launcherApps.getShortcutIcon(shortcut) } // Fall back to extract from message @@ -290,6 +278,11 @@ class IconManager @Inject constructor( } } + // Fall back to notification large icon if available + if (ic == null) { + ic = entry.sbn.notification.getLargeIcon() + } + // Revert to small icon if still not available if (ic == null) { ic = entry.sbn.notification.smallIcon diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index d884bdd47930..9e7bf6220546 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3071,7 +3071,7 @@ public class NotificationPanelViewController extends PanelViewController { return new TouchHandler() { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) { + if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) { return false; } initDownStates(event); @@ -3098,7 +3098,8 @@ public class NotificationPanelViewController extends PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { - if (mBlockTouches || (mQs != null && mQs.isCustomizing())) { + if (mBlockTouches || (mQsFullyExpanded && mQs != null + && mQs.disallowPanelTouches())) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt new file mode 100644 index 000000000000..bab93475c8bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout + +/** + * Basically a normal linear layout but doesn't grow its children with weight 1 even when its + * measured with exactly. + */ +class NeverExactlyLinearLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + + val (widthExactly, usedWidthSpec, width) = getNonExactlyMeasureSpec(widthMeasureSpec) + val (heightExactly, usedHeightSpec, height) = getNonExactlyMeasureSpec(heightMeasureSpec) + + super.onMeasure(usedWidthSpec, usedHeightSpec) + if (widthExactly || heightExactly) { + val newWidth = if (widthExactly) width else measuredWidth + val newHeight = if (heightExactly) height else measuredHeight + setMeasuredDimension(newWidth, newHeight) + } + } + + /** + * Obtain a measurespec that's not exactly + * + * @return a triple, where we return 1. if this was exactly, 2. the new measurespec, 3. the size + * of the measurespec + */ + private fun getNonExactlyMeasureSpec(measureSpec: Int): Triple<Boolean, Int, Int> { + var newSpec = measureSpec + val isExactly = MeasureSpec.getMode(measureSpec) == MeasureSpec.EXACTLY + val size = MeasureSpec.getSize(measureSpec) + if (isExactly) { + newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST) + } + return Triple(isExactly, newSpec, size) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 7b114525adcd..32e3a7fc032e 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -19,13 +19,11 @@ package com.android.systemui.wm; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; @@ -38,7 +36,6 @@ import android.view.WindowInsets; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import com.android.internal.view.IInputMethodManager; import com.android.systemui.TransactionPool; import com.android.systemui.dagger.qualifiers.Main; @@ -355,16 +352,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged dispatchEndPositioning(mDisplayId, mCancelled, t); if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { t.hide(mImeSourceControl.getLeash()); - final IInputMethodManager imms = getImms(); - if (imms != null) { - try { - // Remove the IME surface to make the insets invisible for - // non-client controlled insets. - imms.removeImeSurface(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to remove IME surface.", e); - } - } } t.apply(); mTransactionPool.release(t); @@ -395,9 +382,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged * Called when the IME position is starting to animate. * * @param hiddenTop The y position of the top of the IME surface when it is hidden. - * @param shownTop The y position of the top of the IME surface when it is shown. - * @param showing {@code true} when we are animating from hidden to shown, {@code false} - * when animating from shown to hidden. + * @param shownTop The y position of the top of the IME surface when it is shown. + * @param showing {@code true} when we are animating from hidden to shown, {@code false} + * when animating from shown to hidden. */ default void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, SurfaceControl.Transaction t) {} @@ -419,9 +406,4 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged default void onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) {} } - - public IInputMethodManager getImms() { - return IInputMethodManager.Stub.asInterface( - ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 1e48b990b19d..36398a6fc122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -92,6 +92,7 @@ import com.android.systemui.util.FloatingContentCoordinator; import com.google.common.collect.ImmutableList; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -303,6 +304,10 @@ public class BubbleControllerTest extends SysuiTestCase { public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -330,6 +335,10 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */ false, /* showInShade */ true); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -407,6 +416,7 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test + @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly mEntryListener.onPendingEntryAdded(mRow.getEntry()); @@ -431,15 +441,16 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Switch which bubble is expanded - mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( + mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -541,27 +552,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -837,6 +848,12 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); mBubbleController.updateBubble( mRow3.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey())) + .thenReturn(mRow3.getEntry()); assertEquals(mBubbleData.getBubbles().size(), 3); mBubbleData.setMaxOverflowBubbles(1); @@ -906,6 +923,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with a bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -925,6 +944,8 @@ public class BubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -946,6 +967,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with two (non-bubble) children and one bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index 72f816ff56b5..be03923e7264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -86,8 +86,7 @@ public class BubbleTest extends SysuiTestCase { final String msg = "Hello there!"; doReturn(Notification.Style.class).when(mNotif).getNotificationStyle(); mExtras.putCharSequence(Notification.EXTRA_TEXT, msg); - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -98,8 +97,7 @@ public class BubbleTest extends SysuiTestCase { mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg); // Should be big text, not the small text. - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -107,8 +105,7 @@ public class BubbleTest extends SysuiTestCase { doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); // Media notifs don't get update messages. - assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -124,7 +121,7 @@ public class BubbleTest extends SysuiTestCase { // Should be the last one only. assertEquals("Really? I prefer them that way.", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); + BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -139,11 +136,8 @@ public class BubbleTest extends SysuiTestCase { "Oh, hello!", 0, "Mady").toBundle()}); // Should be the last one only. - assertEquals("Oh, hello!", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); - assertEquals("Mady", - BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).senderName); + assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 0be24729dff9..1c70db3a548e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -301,6 +302,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testRemoveBubble_withDismissedNotif_notInOverflow() { mEntryListener.onEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); @@ -364,6 +367,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { } @Test + @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly mEntryListener.onEntryAdded(mRow.getEntry()); @@ -386,14 +390,14 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { true, mRow.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -486,27 +490,27 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -765,6 +769,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -783,6 +789,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -805,6 +813,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); // WHEN the summary is dismissed diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index 1d02b8dba910..9b8fd11febe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -32,7 +32,7 @@ class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"), BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index f9d611c2bb33..76c58339726c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,10 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0) + private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", + "key-2", 0, 16537428, "title") + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 49467874dd8b..81687c7fbe1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -54,12 +54,12 @@ class BubbleXmlHelperTest : SysuiTestCase() { @Test fun testReadXml() { val src = """ - <?xml version='1.0' encoding='utf-8' standalone='yes' ?> - <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> - </bs> +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<bs> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +</bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 05b31c86559b..cbb0711f78f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.util.animation.UniqueObjectHostView; import org.junit.Before; import org.junit.Test; @@ -108,12 +109,14 @@ public class QSPanelTest extends SysuiTestCase { mDependency.injectMockDependency(SecurityController.class); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class)); + when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext())); mUiEventLogger = new UiEventLoggerFake(); mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher, mQSLogger, mMediaHost, mUiEventLogger); + mQsPanel.onFinishInflate(); // Provides a parent with non-zero size for QSPanel mParentView = new FrameLayout(mContext); mParentView.addView(mQsPanel); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt new file mode 100644 index 000000000000..b63e66f1ebe3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.icon; + +import android.app.ActivityManager; +import android.app.Notification; +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.Person +import android.content.pm.LauncherApps +import android.content.pm.ShortcutInfo +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.os.SystemClock +import android.os.UserHandle +import android.testing.AndroidTestingRunner; +import androidx.test.InstrumentationRegistry +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +import org.junit.runner.RunWith; +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyInt +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IconManagerTest: SysuiTestCase() { + companion object { + private const val TEST_PACKAGE_NAME = "test"; + private const val TEST_UID = 0; + } + + + private var id = 0 + private val context = InstrumentationRegistry.getTargetContext(); + @Mock private lateinit var shortcut: ShortcutInfo + @Mock private lateinit var shortcutIc: Icon + @Mock private lateinit var messageIc: Icon + @Mock private lateinit var largeIc: Icon + @Mock private lateinit var smallIc: Icon + @Mock private lateinit var drawable: Drawable + @Mock private lateinit var row: ExpandableNotificationRow + + @Mock private lateinit var notifCollection: CommonNotifCollection + @Mock private lateinit var launcherApps: LauncherApps + + private val iconBuilder = IconBuilder(context) + + private lateinit var iconManager: IconManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(shortcutIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(messageIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(largeIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(smallIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + + `when`(shortcut.icon).thenReturn(shortcutIc) + `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc) + + iconManager = IconManager(notifCollection, launcherApps, iconBuilder) + } + + @Test + fun testCreateIcons_importantConversation_shortcutIcon() { + val entry = notificationEntry(true, true, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc) + } + + @Test + fun testCreateIcons_importantConversation_messageIcon() { + val entry = notificationEntry(false, true, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, messageIc) + } + + @Test + fun testCreateIcons_importantConversation_largeIcon() { + val entry = notificationEntry(false, false, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, largeIc) + } + + @Test + fun testCreateIcons_importantConversation_smallIcon() { + val entry = notificationEntry(false, false, false) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc) + } + + @Test + fun testCreateIcons_notImportantConversation() { + val entry = notificationEntry(true, true, true) + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc) + } + + @Test + fun testCreateIcons_sensitiveImportantConversation() { + val entry = notificationEntry(true, false, false) + entry?.setSensitive(true, true); + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc) + assertEquals(entry?.icons?.shelfIcon?.sourceIcon, smallIc) + assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc) + } + + @Test + fun testUpdateIcons_sensitivityChange() { + val entry = notificationEntry(true, false, false) + entry?.channel?.isImportantConversation = true + entry?.setSensitive(true, true); + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc) + entry?.setSensitive(false, false); + entry?.let { + iconManager.updateIcons(it) + } + assertEquals(entry?.icons?.shelfIcon?.sourceIcon, shortcutIc) + } + + private fun notificationEntry( + hasShortcut: Boolean, + hasMessage: Boolean, + hasLargeIcon: Boolean + ): NotificationEntry? { + val n = Notification.Builder(mContext, "id") + .setSmallIcon(smallIc) + .setContentTitle("Title") + .setContentText("Text") + + if (hasMessage) { + n.style = Notification.MessagingStyle("") + .addMessage(Notification.MessagingStyle.Message( + "", + SystemClock.currentThreadTimeMillis(), + Person.Builder().setIcon(messageIc).build() + )) + } + + if (hasLargeIcon) { + n.setLargeIcon(largeIc) + } + + val builder = NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setId(id++) + .setNotification(n.build()) + .setChannel(NotificationChannel("id", "", IMPORTANCE_DEFAULT)) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + + if (hasShortcut) { + builder.setShortcutInfo(shortcut) + } + + val entry = builder.build() + entry.row = row + entry.setSensitive(false, true); + return entry + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index b647a1ab9873..65a13016c9b6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -207,7 +207,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; static final int MSG_INITIALIZE_IME = 1040; static final int MSG_CREATE_SESSION = 1050; - static final int MSG_REMOVE_IME_SURFACE = 1060; static final int MSG_START_INPUT = 2000; @@ -3947,12 +3946,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @Override - public void removeImeSurface() { - mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null); - mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); - } - @BinderThread private void notifyUserAction(@NonNull IBinder token) { if (DEBUG) { @@ -4223,15 +4216,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub args.recycle(); return true; } - case MSG_REMOVE_IME_SURFACE: { - try { - if (mEnabledSession != null && mEnabledSession.session != null) { - mEnabledSession.session.removeImeSurface(); - } - } catch (RemoteException e) { - } - return true; - } // --------------------------------------------------------- case MSG_START_INPUT: { diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index d025b0f4ece5..6c415ca326c6 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1347,13 +1347,14 @@ public final class MultiClientInputMethodManagerService { for (WindowInfo windowInfo : clientInfo.mWindowMap.values()) { if (windowInfo.mWindowHandle == targetWindowHandle) { final IBinder targetWindowToken = windowInfo.mWindowToken; - // TODO(yukawa): Report targetWindowToken and targetWindowToken to WMS. if (DEBUG) { Slog.v(TAG, "reportImeWindowTarget" + " clientId=" + clientId + " imeWindowToken=" + imeWindowToken + " targetWindowToken=" + targetWindowToken); } + mIWindowManagerInternal.updateInputMethodTargetWindow( + imeWindowToken, targetWindowToken); } } // not found. @@ -1462,12 +1463,6 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void removeImeSurface() { - reportNotSupported(); - } - - @BinderThread - @Override public boolean showSoftInput( IInputMethodClient client, IBinder token, int flags, ResultReceiver resultReceiver) { @@ -1496,6 +1491,9 @@ public final class MultiClientInputMethodManagerService { case InputMethodClientState.ALREADY_SENT_BIND_RESULT: try { clientInfo.mMSInputMethodSession.showSoftInput(flags, resultReceiver); + + // Forcing WM to show IME on imeTargetWindow + mWindowManagerInternal.showImePostLayout(token); } catch (RemoteException e) { } break; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a85bd060d65e..53aad2351a32 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2829,14 +2829,12 @@ public class CarrierConfigManager { /** * A list of 4 customized LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds. * - * 4 threshold integers must be within the boundaries [-200, 300], and the levels are: - * "NONE: [-200, threshold1)" + * 4 threshold integers must be within the boundaries [-20 dB, 30 dB], and the levels are: + * "NONE: [-20, threshold1)" * "POOR: [threshold1, threshold2)" * "MODERATE: [threshold2, threshold3)" * "GOOD: [threshold3, threshold4)" - * "EXCELLENT: [threshold4, 300]" - * Note: the unit of the values is 10*db; it is derived by multiplying 10 on the original dB - * value reported by modem. + * "EXCELLENT: [threshold4, 30]" * * This key is considered invalid if the format is violated. If the key is invalid or * not configured, a default value set will apply. @@ -4198,10 +4196,10 @@ public class CarrierConfigManager { }); sDefaults.putIntArray(KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY, new int[] { - -30, /* SIGNAL_STRENGTH_POOR */ - 10, /* SIGNAL_STRENGTH_MODERATE */ - 45, /* SIGNAL_STRENGTH_GOOD */ - 130 /* SIGNAL_STRENGTH_GREAT */ + -3, /* SIGNAL_STRENGTH_POOR */ + 1, /* SIGNAL_STRENGTH_MODERATE */ + 5, /* SIGNAL_STRENGTH_GOOD */ + 13 /* SIGNAL_STRENGTH_GREAT */ }); sDefaults.putIntArray(KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY, new int[] { diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 2529387b19b3..c26936e4bdd0 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -118,7 +118,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P * @param rssi in dBm [-113,-51], UNKNOWN * @param rsrp in dBm [-140,-43], UNKNOWN * @param rsrq in dB [-34, 3], UNKNOWN - * @param rssnr in 10*dB [-200, +300], UNKNOWN + * @param rssnr in dB [-20, +30], UNKNOWN * @param cqi [0, 15], UNKNOWN * @param timingAdvance [0, 1282], UNKNOWN * @@ -131,7 +131,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mSignalStrength = mRssi; mRsrp = inRangeOrUnavailable(rsrp, -140, -43); mRsrq = inRangeOrUnavailable(rsrq, -34, 3); - mRssnr = inRangeOrUnavailable(rssnr, -200, 300); + mRssnr = inRangeOrUnavailable(rssnr, -20, 30); mCqi = inRangeOrUnavailable(cqi, 0, 15); mTimingAdvance = inRangeOrUnavailable(timingAdvance, 0, 1282); updateLevel(null, null); @@ -143,7 +143,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P this(convertRssiAsuToDBm(lte.signalStrength), lte.rsrp != CellInfo.UNAVAILABLE ? -lte.rsrp : lte.rsrp, lte.rsrq != CellInfo.UNAVAILABLE ? -lte.rsrq : lte.rsrq, - lte.rssnr, lte.cqi, lte.timingAdvance); + convertRssnrUnitFromTenDbToDB(lte.rssnr), lte.cqi, lte.timingAdvance); } /** @hide */ @@ -208,10 +208,10 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P }; // Lifted from Default carrier configs and max range of RSSNR private static final int[] sRssnrThresholds = new int[] { - -30, /* SIGNAL_STRENGTH_POOR */ - 10, /* SIGNAL_STRENGTH_MODERATE */ - 45, /* SIGNAL_STRENGTH_GOOD */ - 130 /* SIGNAL_STRENGTH_GREAT */ + -3, /* SIGNAL_STRENGTH_POOR */ + 1, /* SIGNAL_STRENGTH_MODERATE */ + 5, /* SIGNAL_STRENGTH_GOOD */ + 13 /* SIGNAL_STRENGTH_GREAT */ }; private static final int sRsrpBoost = 0; @@ -556,6 +556,10 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P Rlog.w(LOG_TAG, s); } + private static int convertRssnrUnitFromTenDbToDB(int rssnr) { + return rssnr / 10; + } + private static int convertRssiAsuToDBm(int rssiAsu) { if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN) { return CellInfo.UNAVAILABLE; diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java index 4273f5a4a16e..af62ba4b93a1 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -19,8 +19,8 @@ package android.telephony; import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; - import android.telephony.Annotation.NetworkType; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -241,13 +241,13 @@ public final class PhysicalChannelConfig implements Parcelable { .append(",mCellBandwidthDownlinkKhz=") .append(mCellBandwidthDownlinkKhz) .append(",mRat=") - .append(mRat) + .append(TelephonyManager.getNetworkTypeName(mRat)) .append(",mFrequencyRange=") - .append(mFrequencyRange) + .append(ServiceState.frequencyRangeToString(mFrequencyRange)) .append(",mChannelNumber=") .append(mChannelNumber) .append(",mContextIds=") - .append(mContextIds.toString()) + .append(Arrays.toString(mContextIds)) .append(",mPhysicalCellId=") .append(mPhysicalCellId) .append("}") diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index c6b06b467782..9e2ba6875577 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1033,6 +1033,26 @@ public class ServiceState implements Parcelable { } /** + * Convert frequency range into string + * + * @param range The cellular frequency range + * @return Frequency range in string format + * + * @hide + */ + public static @NonNull String frequencyRangeToString(@FrequencyRange int range) { + switch (range) { + case FREQUENCY_RANGE_UNKNOWN: return "UNKNOWN"; + case FREQUENCY_RANGE_LOW: return "LOW"; + case FREQUENCY_RANGE_MID: return "MID"; + case FREQUENCY_RANGE_HIGH: return "HIGH"; + case FREQUENCY_RANGE_MMWAVE: return "MMWAVE"; + default: + return Integer.toString(range); + } + } + + /** * Convert RIL Service State to String * * @param serviceState diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fadebaa7bb8a..ee146089b852 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -8197,6 +8197,140 @@ public class TelephonyManager { return false; } + /** @hide */ + @IntDef({ + ALLOWED_NETWORK_TYPES_REASON_POWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AllowedNetworkTypesReason{} + + /** + * To indicate allowed network type change is requested by power manager. + * Power Manger configuration won't affect the settings configured through + * {@link setAllowedNetworkTypes} and will result in allowing network types that are in both + * configurations (i.e intersection of both sets). + * @hide + */ + public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 0; + + /** + * Set the allowed network types of the device and + * provide the reason triggering the allowed network change. + * This can be called for following reasons + * <ol> + * <li>Allowed network types control by power manager + * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER} + * </ol> + * This API will result in allowing an intersection of allowed network types for all reasons, + * including the configuration done through {@link setAllowedNetworkTypes}. + * While this API and {@link setAllowedNetworkTypes} is controlling allowed network types + * on device, user preference will still be set through {@link #setPreferredNetworkTypeBitmask}. + * Thus resultant network type configured on modem will be an intersection of the network types + * from setAllowedNetworkTypesForReason, {@link setAllowedNetworkTypes} + * and {@link #setPreferredNetworkTypeBitmask}. + * + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes The bitmask of allowed network types. + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason, + @NetworkTypeBitMask long allowedNetworkTypes) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.setAllowedNetworkTypesForReason(getSubId(), reason, + allowedNetworkTypes); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Get the allowed network types for certain reason. + * + * {@link #getAllowedNetworkTypesForReason} returns allowed network type for a + * specific reason. For effective allowed network types configured on device, + * query {@link getEffectiveAllowedNetworkTypes} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + *s + * @param reason the reason the allowed network type change is taking place + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getAllowedNetworkTypesForReason( + @AllowedNetworkTypesReason int reason) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getAllowedNetworkTypesForReason(getSubId(), reason); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + + /** + * Get bit mask of all network types. + * + * @return bit mask of all network types + * @hide + */ + public static @NetworkTypeBitMask long getAllNetworkTypesBitmask() { + return NETWORK_STANDARDS_FAMILY_BITMASK_3GPP | NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2; + } + + /** + * Get the allowed network types configured on the device. + * This API will return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getEffectiveAllowedNetworkTypes(getSubId()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getEffectiveAllowedNetworkTypes RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 369020033a59..b70937cee8a1 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -956,6 +956,35 @@ interface ITelephony { boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes); /** + * Get the allowed network types for certain reason. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @return allowedNetworkTypes the allowed network types. + */ + long getAllowedNetworkTypesForReason(int subId, int reason); + + /** + * Get the effective allowed network types on the device. This API will + * return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * @param subId the id of the subscription. + * @return allowedNetworkTypes the allowed network types. + */ + long getEffectiveAllowedNetworkTypes(int subId); + + /** + * Set the allowed network types and provide the reason triggering the allowed network change. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes the allowed network types. + * @return true on success; false on any failure. + */ + boolean setAllowedNetworkTypesForReason(int subId, int reason, long allowedNetworkTypes); + + /** * Set the preferred network type. * Used for device configuration by some CDMA operators. * |