1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenshot.ui.viewmodel
18 
19 import android.graphics.Bitmap
20 import android.graphics.Rect
21 import android.graphics.drawable.Drawable
22 import android.util.Log
23 import android.view.accessibility.AccessibilityManager
24 import kotlinx.coroutines.flow.MutableStateFlow
25 import kotlinx.coroutines.flow.StateFlow
26 
27 class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
28     private val _preview = MutableStateFlow<Bitmap?>(null)
29     val preview: StateFlow<Bitmap?> = _preview
30     private val _scrollingScrim = MutableStateFlow<Bitmap?>(null)
31     val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim
32     private val _badge = MutableStateFlow<Drawable?>(null)
33     val badge: StateFlow<Drawable?> = _badge
34     private val _previewAction = MutableStateFlow<PreviewAction?>(null)
35     val previewAction: StateFlow<PreviewAction?> = _previewAction
36     private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
37     val actions: StateFlow<List<ActionButtonViewModel>> = _actions
38     private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
39     val animationState: StateFlow<AnimationState> = _animationState
40 
41     private val _isAnimating = MutableStateFlow(false)
42     val isAnimating: StateFlow<Boolean> = _isAnimating
43     private val _scrollableRect = MutableStateFlow<Rect?>(null)
44     val scrollableRect: StateFlow<Rect?> = _scrollableRect
45     val showDismissButton: Boolean
46         get() = accessibilityManager.isEnabled
47 
setScreenshotBitmapnull48     fun setScreenshotBitmap(bitmap: Bitmap?) {
49         _preview.value = bitmap
50     }
51 
setScrollingScrimBitmapnull52     fun setScrollingScrimBitmap(bitmap: Bitmap?) {
53         _scrollingScrim.value = bitmap
54     }
55 
setScreenshotBadgenull56     fun setScreenshotBadge(badge: Drawable?) {
57         _badge.value = badge
58     }
59 
setPreviewActionnull60     fun setPreviewAction(previewAction: PreviewAction) {
61         _previewAction.value = previewAction
62     }
63 
addActionnull64     fun addAction(
65         actionAppearance: ActionButtonAppearance,
66         showDuringEntrance: Boolean,
67         onClicked: (() -> Unit)
68     ): Int {
69         val actionList = _actions.value.toMutableList()
70         val action =
71             ActionButtonViewModel.withNextId(actionAppearance, showDuringEntrance, onClicked)
72         actionList.add(action)
73         _actions.value = actionList
74         return action.id
75     }
76 
setActionVisibilitynull77     fun setActionVisibility(actionId: Int, visible: Boolean) {
78         val actionList = _actions.value.toMutableList()
79         val index = actionList.indexOfFirst { it.id == actionId }
80         if (index >= 0) {
81             actionList[index] =
82                 ActionButtonViewModel(
83                     actionList[index].appearance,
84                     actionId,
85                     visible,
86                     actionList[index].showDuringEntrance,
87                     actionList[index].onClicked
88                 )
89             _actions.value = actionList
90         } else {
91             Log.w(TAG, "Attempted to update unknown action id $actionId")
92         }
93     }
94 
updateActionAppearancenull95     fun updateActionAppearance(actionId: Int, appearance: ActionButtonAppearance) {
96         val actionList = _actions.value.toMutableList()
97         val index = actionList.indexOfFirst { it.id == actionId }
98         if (index >= 0) {
99             actionList[index] =
100                 ActionButtonViewModel(
101                     appearance,
102                     actionId,
103                     actionList[index].visible,
104                     actionList[index].showDuringEntrance,
105                     actionList[index].onClicked
106                 )
107             _actions.value = actionList
108         } else {
109             Log.w(TAG, "Attempted to update unknown action id $actionId")
110         }
111     }
112 
removeActionnull113     fun removeAction(actionId: Int) {
114         val actionList = _actions.value.toMutableList()
115         if (actionList.removeIf { it.id == actionId }) {
116             // Update if something was removed.
117             _actions.value = actionList
118         } else {
119             Log.w(TAG, "Attempted to remove unknown action id $actionId")
120         }
121     }
122 
123     // TODO: this should be handled entirely within the view binder.
setAnimationStatenull124     fun setAnimationState(state: AnimationState) {
125         _animationState.value = state
126     }
127 
setIsAnimatingnull128     fun setIsAnimating(isAnimating: Boolean) {
129         _isAnimating.value = isAnimating
130     }
131 
setScrollableRectnull132     fun setScrollableRect(rect: Rect?) {
133         _scrollableRect.value = rect
134     }
135 
resetnull136     fun reset() {
137         _preview.value = null
138         _scrollingScrim.value = null
139         _badge.value = null
140         _previewAction.value = null
141         _actions.value = listOf()
142         _animationState.value = AnimationState.NOT_STARTED
143         _isAnimating.value = false
144         _scrollableRect.value = null
145     }
146 
147     companion object {
148         const val TAG = "ScreenshotViewModel"
149     }
150 }
151 
152 data class PreviewAction(
153     val contentDescription: CharSequence,
154     val onClick: () -> Unit,
155 )
156 
157 enum class AnimationState {
158     NOT_STARTED,
159     ENTRANCE_STARTED, // The first 200ms of the entrance animation
160     ENTRANCE_REVEAL, // The rest of the entrance animation
161     ENTRANCE_COMPLETE,
162 }
163