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

Commit 8e03c9f9 authored by Steve Elliott's avatar Steve Elliott Committed by Automerger Merge Worker
Browse files

Merge "Delete unused PeopleHub code" into tm-qpr-dev am: 1035f8a3

parents 9e6d9390 1035f8a3
Loading
Loading
Loading
Loading
+0 −102
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2019 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<com.android.systemui.statusbar.notification.stack.PeopleHubView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/notification_section_header_height"
    android:paddingStart="4dp"
    android:paddingEnd="4dp"
    android:focusable="true"
    android:clickable="true"
>

    <LinearLayout
        android:id="@+id/people_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginEnd="8dp"
        android:gravity="bottom"
        android:orientation="horizontal"
        android:forceHasOverlappingRendering="false"
        android:clipChildren="false">

        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="start|center_vertical"
            android:layout_weight="1"
            android:forceHasOverlappingRendering="false">

            <TextView
                android:id="@+id/header_label"
                style="@style/TextAppearance.NotificationSectionHeaderButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/notification_section_header_conversations"
            />

        </FrameLayout>

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="8dp"
            android:scaleType="fitCenter"
            android:forceHasOverlappingRendering="false"
            android:visibility="gone"
        />

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="8dp"
            android:scaleType="fitCenter"
            android:forceHasOverlappingRendering="false"
            android:visibility="gone"
        />

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="8dp"
            android:scaleType="fitCenter"
            android:forceHasOverlappingRendering="false"
            android:visibility="gone"
        />

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="8dp"
            android:scaleType="fitCenter"
            android:forceHasOverlappingRendering="false"
            android:visibility="gone"
        />

        <ImageView
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="8dp"
            android:scaleType="fitCenter"
            android:forceHasOverlappingRendering="false"
            android:visibility="gone"
        />

    </LinearLayout>

</com.android.systemui.statusbar.notification.stack.PeopleHubView>
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,16 +16,34 @@

package com.android.systemui.statusbar.notification.people

object EmptySubscription : Subscription {
    override fun unsubscribe() {}
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.NotificationPersonExtractorPlugin
import com.android.systemui.statusbar.policy.ExtensionController
import javax.inject.Inject

interface NotificationPersonExtractor {
    fun isPersonNotification(sbn: StatusBarNotification): Boolean
}

class FakeDataSource<T>(
    private val data: T,
    private val subscription: Subscription = EmptySubscription
) : DataSource<T> {
    override fun registerListener(listener: DataListener<T>): Subscription {
        listener.onDataChanged(data)
        return subscription
@SysUISingleton
class NotificationPersonExtractorPluginBoundary @Inject constructor(
    extensionController: ExtensionController
) : NotificationPersonExtractor {

    private var plugin: NotificationPersonExtractorPlugin? = null

    init {
        plugin = extensionController
                .newExtension(NotificationPersonExtractorPlugin::class.java)
                .withPlugin(NotificationPersonExtractorPlugin::class.java)
                .withCallback { extractor ->
                    plugin = extractor
                }
                .build()
                .get()
    }

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

package com.android.systemui.statusbar.notification.people

import android.graphics.drawable.Drawable

/**
 * `ViewModel` for PeopleHub view.
 *
 * @param people ViewModels for individual people in PeopleHub, in order that they should be
 *  displayed
 * @param isVisible Whether or not the whole PeopleHub UI is visible
 **/
data class PeopleHubViewModel(val people: Sequence<PersonViewModel>, val isVisible: Boolean)

/** `ViewModel` for a single "Person' in PeopleHub. */
data class PersonViewModel(
    val name: CharSequence,
    val icon: Drawable,
    val onClick: () -> Unit
)

/**
 * `Model` for PeopleHub.
 *
 * @param people Models for individual people in PeopleHub, in order that they should be displayed
 **/
data class PeopleHubModel(val people: Collection<PersonModel>)

/** `Model` for a single "Person" in PeopleHub. */
data class PersonModel(
    val key: PersonKey,
    val userId: Int,
    // TODO: these should live in the ViewModel
    val name: CharSequence,
    val avatar: Drawable,
    val clickRunnable: Runnable
)

/** Unique identifier for a Person in PeopleHub. */
typealias PersonKey = String
 No newline at end of file
+1 −20
Original line number Diff line number Diff line
@@ -21,25 +21,6 @@ import dagger.Module

@Module
abstract class PeopleHubModule {

    @Binds
    abstract fun peopleHubSectionFooterViewAdapter(
        impl: PeopleHubViewAdapterImpl
    ): PeopleHubViewAdapter

    @Binds
    abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>

    @Binds
    abstract fun peopleHubSettingChangeDataSource(
        impl: PeopleHubSettingChangeDataSourceImpl
    ): DataSource<Boolean>

    @Binds
    abstract fun peopleHubViewModelFactoryDataSource(
        impl: PeopleHubViewModelFactoryDataSourceImpl
    ): DataSource<PeopleHubViewModelFactory>

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

package com.android.systemui.statusbar.notification.people

import android.app.Notification
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.graphics.drawable.Drawable
import android.os.UserManager
import android.service.notification.NotificationListenerService
import android.service.notification.NotificationListenerService.REASON_SNOOZED
import android.service.notification.StatusBarNotification
import android.util.IconDrawableFactory
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.android.internal.widget.MessagingGroup
import com.android.settingslib.notification.ConversationIconFactory
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NotificationPersonExtractorPlugin
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
import java.util.concurrent.Executor
import javax.inject.Inject

private const val MAX_STORED_INACTIVE_PEOPLE = 10

interface NotificationPersonExtractor {
    fun extractPerson(sbn: StatusBarNotification): PersonModel?
    fun extractPersonKey(sbn: StatusBarNotification): String?
    fun isPersonNotification(sbn: StatusBarNotification): Boolean
}

@SysUISingleton
class NotificationPersonExtractorPluginBoundary @Inject constructor(
    extensionController: ExtensionController
) : NotificationPersonExtractor {

    private var plugin: NotificationPersonExtractorPlugin? = null

    init {
        plugin = extensionController
                .newExtension(NotificationPersonExtractorPlugin::class.java)
                .withPlugin(NotificationPersonExtractorPlugin::class.java)
                .withCallback { extractor ->
                    plugin = extractor
                }
                .build()
                .get()
    }

    override fun extractPerson(sbn: StatusBarNotification) =
            plugin?.extractPerson(sbn)?.run {
                PersonModel(key, sbn.user.identifier, name, avatar, clickRunnable)
            }

    override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)

    override fun isPersonNotification(sbn: StatusBarNotification): Boolean =
            plugin?.isPersonNotification(sbn) ?: false
}

@SysUISingleton
class PeopleHubDataSourceImpl @Inject constructor(
    private val notifCollection: CommonNotifCollection,
    private val extractor: NotificationPersonExtractor,
    private val userManager: UserManager,
    launcherApps: LauncherApps,
    packageManager: PackageManager,
    context: Context,
    private val notificationListener: NotificationListener,
    @Background private val bgExecutor: Executor,
    @Main private val mainExecutor: Executor,
    private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
) : DataSource<PeopleHubModel> {

    private var userChangeSubscription: Subscription? = null
    private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
    private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()

    private val iconFactory = run {
        val appContext = context.applicationContext
        ConversationIconFactory(
                appContext,
                launcherApps,
                packageManager,
                IconDrawableFactory.newInstance(appContext),
                appContext.resources.getDimensionPixelSize(
                        R.dimen.notification_guts_conversation_icon_size
                )
        )
    }

    private val notifCollectionListener = object : NotifCollectionListener {
        override fun onEntryAdded(entry: NotificationEntry) = addVisibleEntry(entry)
        override fun onEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) =
            removeVisibleEntry(entry, reason)
    }

    private fun removeVisibleEntry(entry: NotificationEntry, reason: Int) {
        (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
            val userId = entry.sbn.user.identifier
            bgExecutor.execute {
                val parentId = userManager.getProfileParent(userId)?.id ?: userId
                mainExecutor.execute {
                    if (reason == REASON_SNOOZED) {
                        if (peopleHubManagerForUser[parentId]?.migrateActivePerson(key) == true) {
                            updateUi()
                        }
                    } else {
                        peopleHubManagerForUser[parentId]?.removeActivePerson(key)
                    }
                }
            }
        }
    }

    private fun addVisibleEntry(entry: NotificationEntry) {
        entry.extractPerson()?.let { personModel ->
            val userId = entry.sbn.user.identifier
            bgExecutor.execute {
                val parentId = userManager.getProfileParent(userId)?.id ?: userId
                mainExecutor.execute {
                    val manager = peopleHubManagerForUser[parentId]
                            ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) }
                    if (manager.addActivePerson(personModel)) {
                        updateUi()
                    }
                }
            }
        }
    }

    override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
        val register = dataListeners.isEmpty()
        dataListeners.add(listener)
        if (register) {
            userChangeSubscription = notifLockscreenUserMgr.registerListener(
                    object : NotificationLockscreenUserManager.UserChangedListener {
                        override fun onUserChanged(userId: Int) = updateUi()
                        override fun onCurrentProfilesChanged(
                            currentProfiles: SparseArray<UserInfo>?
                        ) = updateUi()
                    })
            notifCollection.addCollectionListener(notifCollectionListener)
        } else {
            getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged)
        }
        return object : Subscription {
            override fun unsubscribe() {
                dataListeners.remove(listener)
                if (dataListeners.isEmpty()) {
                    userChangeSubscription?.unsubscribe()
                    userChangeSubscription = null
                    notifCollection.removeCollectionListener(notifCollectionListener)
                }
            }
        }
    }

    private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? {
        val currentUserId = notifLockscreenUserMgr.currentUserId
        val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel()
                ?: return null
        val currentProfiles = notifLockscreenUserMgr.currentProfiles
        return model.copy(people = model.people.filter { person ->
            currentProfiles[person.userId]?.isQuietModeEnabled == false
        })
    }

    private fun updateUi() {
        val model = getPeopleHubModelForCurrentUser() ?: return
        for (listener in dataListeners) {
            listener.onDataChanged(model)
        }
    }

    private fun NotificationEntry.extractPerson(): PersonModel? {
        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
        if (type == TYPE_NON_PERSON) {
            return null
        }
        val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
        val extras = sbn.notification.extras
        val name = ranking.conversationShortcutInfo?.label
                ?: extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE)
                ?: extras.getCharSequence(Notification.EXTRA_TITLE)
                ?: return null
        val drawable = ranking.getIcon(iconFactory, sbn)
                ?: iconFactory.getConversationDrawable(
                        extractAvatarFromRow(this),
                        sbn.packageName,
                        sbn.uid,
                        ranking.channel.isImportantConversation
                )
        return PersonModel(key, sbn.user.identifier, name, drawable, clickRunnable)
    }

    private fun NotificationListenerService.Ranking.getIcon(
        iconFactory: ConversationIconFactory,
        sbn: StatusBarNotification
    ): Drawable? =
            conversationShortcutInfo?.let { conversationShortcutInfo ->
                iconFactory.getConversationDrawable(
                        conversationShortcutInfo,
                        sbn.packageName,
                        sbn.uid,
                        channel.isImportantConversation
                )
            }

    private fun NotificationEntry.extractPersonKey(): PersonKey? {
        // TODO migrate to shortcut id when snoozing is conversation wide
        val type = peopleNotificationIdentifier.getPeopleNotificationType(this)
        return if (type != TYPE_NON_PERSON) key else null
    }
}

private fun NotificationLockscreenUserManager.registerListener(
    listener: NotificationLockscreenUserManager.UserChangedListener
): Subscription {
    addUserChangedListener(listener)
    return object : Subscription {
        override fun unsubscribe() {
            removeUserChangedListener(listener)
        }
    }
}

class PeopleHubManager {

    // People currently visible in the notification shade, and so are not in the hub
    private val activePeople = mutableMapOf<PersonKey, PersonModel>()

    // People that were once "active" and have been dismissed, and so can be displayed in the hub
    private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)

    fun migrateActivePerson(key: PersonKey): Boolean {
        activePeople.remove(key)?.let { data ->
            if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
                inactivePeople.removeLast()
            }
            inactivePeople.addFirst(data)
            return true
        }
        return false
    }

    fun removeActivePerson(key: PersonKey) {
        activePeople.remove(key)
    }

    fun addActivePerson(person: PersonModel): Boolean {
        activePeople[person.key] = person
        return inactivePeople.removeIf { it.key == person.key }
    }

    fun getPeopleHubModel(): PeopleHubModel = PeopleHubModel(inactivePeople)
}

private val ViewGroup.children
    get(): Sequence<View> = sequence {
        for (i in 0 until childCount) {
            yield(getChildAt(i))
        }
    }

private fun ViewGroup.childrenWithId(id: Int): Sequence<View> = children.filter { it.id == id }

fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
        entry.row
                ?.childrenWithId(R.id.expanded)
                ?.mapNotNull { it as? ViewGroup }
                ?.flatMap {
                    it.childrenWithId(com.android.internal.R.id.status_bar_latest_event_content)
                }
                ?.mapNotNull {
                    it.findViewById<ViewGroup>(com.android.internal.R.id.notification_messaging)
                }
                ?.mapNotNull { messagesView ->
                    messagesView.children
                            .mapNotNull { it as? MessagingGroup }
                            .lastOrNull()
                            ?.findViewById<ImageView>(com.android.internal.R.id.message_icon)
                            ?.drawable
                }
                ?.firstOrNull()
Loading