Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e9557c25 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Do less on the main thread" into udc-qpr-dev

parents 876dbf2a e1713e60
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -21,12 +21,16 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;

import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -71,6 +75,10 @@ import javax.inject.Named;
@NotificationRowScope
public class ExpandableNotificationRowController implements NotifViewController {
    private static final String TAG = "NotifRowController";

    static final Uri BUBBLES_SETTING_URI =
            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
    private static final String BUBBLES_SETTING_ENABLED_VALUE = "1";
    private final ExpandableNotificationRow mView;
    private final NotificationListContainer mListContainer;
    private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
@@ -104,6 +112,23 @@ public class ExpandableNotificationRowController implements NotifViewController
    private final ExpandableNotificationRowDragController mDragController;
    private final NotificationDismissibilityProvider mDismissibilityProvider;
    private final IStatusBarService mStatusBarService;

    private final NotificationSettingsController mSettingsController;

    @VisibleForTesting
    final NotificationSettingsController.Listener mSettingsListener =
            new NotificationSettingsController.Listener() {
                @Override
                public void onSettingChanged(Uri setting, int userId, String value) {
                    if (BUBBLES_SETTING_URI.equals(setting)) {
                        final int viewUserId = mView.getEntry().getSbn().getUserId();
                        if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
                            mView.getPrivateLayout().setBubblesEnabledForUser(
                                    BUBBLES_SETTING_ENABLED_VALUE.equals(value));
                        }
                    }
                }
            };
    private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
            new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                @Override
@@ -201,6 +226,7 @@ public class ExpandableNotificationRowController implements NotifViewController
            FeatureFlags featureFlags,
            PeopleNotificationIdentifier peopleNotificationIdentifier,
            Optional<BubblesManager> bubblesManagerOptional,
            NotificationSettingsController settingsController,
            ExpandableNotificationRowDragController dragController,
            NotificationDismissibilityProvider dismissibilityProvider,
            IStatusBarService statusBarService) {
@@ -229,6 +255,7 @@ public class ExpandableNotificationRowController implements NotifViewController
        mFeatureFlags = featureFlags;
        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
        mBubblesManagerOptional = bubblesManagerOptional;
        mSettingsController = settingsController;
        mDragController = dragController;
        mMetricsLogger = metricsLogger;
        mChildrenContainerLogger = childrenContainerLogger;
@@ -298,12 +325,14 @@ public class ExpandableNotificationRowController implements NotifViewController
                        NotificationMenuRowPlugin.class, false /* Allow multiple */);
                mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
                mStatusBarStateController.addCallback(mStatusBarStateListener);
                mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                mPluginManager.removePluginListener(mView);
                mStatusBarStateController.removeCallback(mStatusBarStateListener);
                mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
            }
        });
    }
+9 −2
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
@@ -65,7 +66,6 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyView;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
import com.android.systemui.wmshell.BubblesManager;

import java.io.PrintWriter;
import java.util.ArrayList;
@@ -134,6 +134,7 @@ public class NotificationContentView extends FrameLayout implements Notification
    private PeopleNotificationIdentifier mPeopleIdentifier;
    private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
    private IStatusBarService mStatusBarService;
    private boolean mBubblesEnabledForUser;

    /**
     * List of listeners for when content views become inactive (i.e. not the showing view).
@@ -1440,12 +1441,17 @@ public class NotificationContentView extends FrameLayout implements Notification
        }
    }

    @Background
    public void setBubblesEnabledForUser(boolean enabled) {
        mBubblesEnabledForUser = enabled;
    }

    @VisibleForTesting
    boolean shouldShowBubbleButton(NotificationEntry entry) {
        boolean isPersonWithShortcut =
                mPeopleIdentifier.getPeopleNotificationType(entry)
                        >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
        return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
        return mBubblesEnabledForUser
                && isPersonWithShortcut
                && entry.getBubbleMetadata() != null;
    }
@@ -2079,6 +2085,7 @@ public class NotificationContentView extends FrameLayout implements Notification
            pw.print("null");
        }
        pw.println();
        pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);

        pw.print("RemoteInputViews { ");
        pw.print(" visibleType: " + mVisibleType);
+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.notification.row;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.SecureSettings;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;

import javax.inject.Inject;

/**
 * Centralized controller for listening to Secure Settings changes and informing in-process
 * listeners, on a background thread.
 */
@SysUISingleton
public class NotificationSettingsController implements Dumpable {

    private final static String TAG = "NotificationSettingsController";
    private final UserTracker mUserTracker;
    private final UserTracker.Callback mCurrentUserTrackerCallback;
    private final Handler mHandler;
    private final ContentObserver mContentObserver;
    private final SecureSettings mSecureSettings;
    private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>();

    @Inject
    public NotificationSettingsController(UserTracker userTracker,
            @Background Handler handler,
            SecureSettings secureSettings,
            DumpManager dumpManager) {
        mUserTracker = userTracker;
        mHandler = handler;
        mSecureSettings = secureSettings;
        mContentObserver = new ContentObserver(mHandler) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                super.onChange(selfChange, uri);
                synchronized (mListeners) {
                    if (mListeners.containsKey(uri)) {
                        for (Listener listener : mListeners.get(uri)) {
                            notifyListener(listener, uri);
                        }
                    }
                }
            }
        };

        mCurrentUserTrackerCallback = new UserTracker.Callback() {
            @Override
            public void onUserChanged(int newUser, Context userContext) {
                synchronized (mListeners) {
                    if (mListeners.size() > 0) {
                        mSecureSettings.unregisterContentObserver(mContentObserver);
                        for (Uri uri : mListeners.keySet()) {
                            mSecureSettings.registerContentObserverForUser(
                                    uri, false, mContentObserver, newUser);
                        }
                    }
                }
            }
        };
        mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));

        dumpManager.registerNormalDumpable(TAG, this);
    }

    /**
     * Register callback whenever the given secure settings changes.
     *
     * On registration, will call back on the provided handler with the current value of
     * the setting.
     */
    public void addCallback(@NonNull Uri uri, @NonNull Listener listener) {
        if (uri == null || listener == null) {
            return;
        }
        synchronized (mListeners) {
            ArrayList<Listener> currentListeners = mListeners.get(uri);
            if (currentListeners == null) {
                currentListeners = new ArrayList<>();
            }
            if (!currentListeners.contains(listener)) {
                currentListeners.add(listener);
            }
            mListeners.put(uri, currentListeners);
            if (currentListeners.size() == 1) {
                mSecureSettings.registerContentObserverForUser(
                        uri, false, mContentObserver, mUserTracker.getUserId());
            }
        }
        mHandler.post(() -> notifyListener(listener, uri));

    }

    public void removeCallback(Uri uri, Listener listener) {
        synchronized (mListeners) {
            ArrayList<Listener> currentListeners = mListeners.get(uri);

            if (currentListeners != null) {
                currentListeners.remove(listener);
            }
            if (currentListeners == null || currentListeners.size() == 0) {
                mListeners.remove(uri);
            }

            if (mListeners.size() == 0) {
                mSecureSettings.unregisterContentObserver(mContentObserver);
            }
        }
    }

    @Override
    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
        synchronized (mListeners) {
            pw.println("Settings Uri Listener List:");
            for (Uri uri : mListeners.keySet()) {
                pw.println("   Uri=" + uri);
                for (Listener listener : mListeners.get(uri)) {
                    pw.println("      Listener=" + listener.getClass().getName());
                }
            }
        }
    }

    private void notifyListener(Listener listener, Uri uri) {
        final String setting = uri == null ? null : uri.getLastPathSegment();
        int userId = mUserTracker.getUserId();
        listener.onSettingChanged(uri, userId, mSecureSettings.getStringForUser(setting, userId));
    }

    /**
     * Listener invoked whenever settings are changed.
     */
    public interface Listener {
        void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value);
    }
}
 No newline at end of file
+88 −2
Original line number Diff line number Diff line
@@ -17,6 +17,10 @@

package com.android.systemui.statusbar.notification.row

import android.app.Notification
import android.net.Uri
import android.os.UserHandle
import android.os.UserHandle.USER_ALL
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -28,13 +32,17 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -45,9 +53,9 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.SystemClock
import com.android.systemui.wmshell.BubblesManager
import java.util.Optional
import junit.framework.Assert
import org.junit.After
import org.junit.Before
@@ -55,7 +63,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import java.util.*
import org.mockito.Mockito.`when` as whenever

@SmallTest
@@ -92,10 +103,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
    private val featureFlags: FeatureFlags = mock()
    private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
    private val bubblesManager: BubblesManager = mock()
    private val settingsController: NotificationSettingsController = mock()
    private val dragController: ExpandableNotificationRowDragController = mock()
    private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
    private val statusBarService: IStatusBarService = mock()

    private lateinit var controller: ExpandableNotificationRowController

    @Before
@@ -132,11 +143,16 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
                featureFlags,
                peopleNotificationIdentifier,
                Optional.of(bubblesManager),
                settingsController,
                dragController,
                dismissibilityProvider,
                statusBarService
            )
        whenever(view.childrenContainer).thenReturn(childrenContainer)

        val notification = Notification.Builder(mContext).build()
        val sbn = SbnBuilder().setNotification(notification).build()
        whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build())
    }

    @After
@@ -204,4 +220,74 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() {
        Mockito.verify(view).removeChildNotification(eq(childView))
        Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
    }

    @Test
    fun registerSettingsListener_forBubbles() {
        controller.init(mock(NotificationEntry::class.java))
        val viewStateObserver = withArgCaptor {
            verify(view).addOnAttachStateChangeListener(capture());
        }
        viewStateObserver.onViewAttachedToWindow(view);
        verify(settingsController).addCallback(any(), any());
    }

    @Test
    fun unregisterSettingsListener_forBubbles() {
        controller.init(mock(NotificationEntry::class.java))
        val viewStateObserver = withArgCaptor {
            verify(view).addOnAttachStateChangeListener(capture());
        }
        viewStateObserver.onViewDetachedFromWindow(view);
        verify(settingsController).removeCallback(any(), any());
    }

    @Test
    fun settingsListener_invalidUri() {
        controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1")

        verify(view, never()).getPrivateLayout()
    }

    @Test
    fun settingsListener_invalidUserId() {
        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1")
        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null)

        verify(view, never()).getPrivateLayout()
    }

    @Test
    fun settingsListener_validUserId() {
        val childView: NotificationContentView = mock()
        whenever(view.privateLayout).thenReturn(childView)

        controller.mSettingsListener.onSettingChanged(
                BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
        verify(childView).setBubblesEnabledForUser(true)

        controller.mSettingsListener.onSettingChanged(
                BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
        verify(childView).setBubblesEnabledForUser(false)
    }

    @Test
    fun settingsListener_userAll() {
        val childView: NotificationContentView = mock()
        whenever(view.privateLayout).thenReturn(childView)

        val notification = Notification.Builder(mContext).build()
        val sbn = SbnBuilder().setNotification(notification)
                .setUser(UserHandle.of(USER_ALL))
                .build()
        whenever(view.entry).thenReturn(NotificationEntryBuilder()
                .setSbn(sbn)
                .setUser(UserHandle.of(USER_ALL))
                .build())

        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
        verify(childView).setBubblesEnabledForUser(true)

        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0")
        verify(childView).setBubblesEnabledForUser(false)
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -250,6 +250,9 @@ class NotificationContentViewTest : SysuiTestCase() {
            .thenReturn(actionListMarginTarget)
        view.setContainingNotification(mockContainingNotification)

        // Given: controller says bubbles are enabled for the user
        view.setBubblesEnabledForUser(true);

        // When: call NotificationContentView.setExpandedChild() to set the expandedChild
        view.expandedChild = mockExpandedChild

@@ -301,6 +304,9 @@ class NotificationContentViewTest : SysuiTestCase() {
        view.expandedChild = mockExpandedChild
        assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))

        // Given: controller says bubbles are enabled for the user
        view.setBubblesEnabledForUser(true);

        // When: call NotificationContentView.onNotificationUpdated() to update the
        // NotificationEntry, which should show bubble button
        view.onNotificationUpdated(createMockNotificationEntry(true))
@@ -405,7 +411,6 @@ class NotificationContentViewTest : SysuiTestCase() {
            val userMock: UserHandle = mock()
            whenever(this.sbn).thenReturn(sbnMock)
            whenever(sbnMock.user).thenReturn(userMock)
            doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
        }

    private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
Loading