1 /*
2  * Copyright (C) 2021 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.animation
18 
19 import android.view.View
20 
21 /** A view that can expand/launch into an app or a dialog. */
22 interface LaunchableView {
23     /**
24      * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
25      * that this view:
26      * - remains invisible during the launch animation given that it is ghosted and already drawn
27      *   somewhere else.
28      * - remains invisible as long as a dialog expanded from it is shown.
29      * - restores its expected visibility once the dialog expanded from it is dismissed.
30      *
31      * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
32      * be restored to its expected value, i.e. it should have the visibility of the last call to
33      * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
34      * or the original view visibility otherwise.
35      *
36      * Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
37      *
38      * @param block whether we should block/postpone all calls to `setVisibility`.
39      */
40     fun setShouldBlockVisibilityChanges(block: Boolean)
41 }
42 
43 /** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
44 class LaunchableViewDelegate(
45     private val view: View,
46 
47     /**
48      * The lambda that should set the actual visibility of [view], usually by calling
49      * super.setVisibility(visibility).
50      */
51     private val superSetVisibility: (Int) -> Unit,
52 ) : LaunchableView {
53     private var blockVisibilityChanges = false
54     private var lastVisibility = view.visibility
55 
56     /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
57     override fun setShouldBlockVisibilityChanges(block: Boolean) {
58         if (block == blockVisibilityChanges) {
59             return
60         }
61 
62         blockVisibilityChanges = block
63         if (block) {
64             // Save the current visibility for later.
65             lastVisibility = view.visibility
66         } else {
67             // Restore the visibility. To avoid accessibility issues, we change the visibility twice
68             // which makes sure that we trigger a visibility flag change (see b/204944038#comment17
69             // for more info).
70             if (lastVisibility == View.VISIBLE) {
71                 superSetVisibility(View.INVISIBLE)
72                 superSetVisibility(View.VISIBLE)
73             } else {
74                 superSetVisibility(View.VISIBLE)
75                 superSetVisibility(lastVisibility)
76             }
77         }
78     }
79 
80     /** Call this when [View.setVisibility] is called. */
81     fun setVisibility(visibility: Int) {
82         if (blockVisibilityChanges) {
83             lastVisibility = visibility
84             return
85         }
86 
87         superSetVisibility(visibility)
88     }
89 }
90