1 /*
2  * Copyright (C) 2022 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 package com.android.systemui.complication;
17 
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.ArgumentMatchers.anyInt;
20 import static org.mockito.ArgumentMatchers.eq;
21 import static org.mockito.Mockito.never;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24 
25 import android.os.UserHandle;
26 import android.provider.Settings;
27 import android.testing.AndroidTestingRunner;
28 import android.view.View;
29 
30 import androidx.constraintlayout.widget.ConstraintLayout;
31 import androidx.lifecycle.LifecycleOwner;
32 import androidx.lifecycle.LiveData;
33 import androidx.lifecycle.Observer;
34 import androidx.test.filters.SmallTest;
35 
36 import com.android.systemui.SysuiTestCase;
37 import com.android.systemui.dreams.DreamOverlayStateController;
38 import com.android.systemui.util.settings.FakeSettings;
39 import com.android.systemui.util.settings.SecureSettings;
40 
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.Captor;
46 import org.mockito.Mock;
47 import org.mockito.MockitoAnnotations;
48 
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashSet;
52 
53 @SmallTest
54 @RunWith(AndroidTestingRunner.class)
55 public class ComplicationHostViewControllerTest extends SysuiTestCase {
56     @Mock
57     ConstraintLayout mComplicationHostView;
58 
59     @Mock
60     LifecycleOwner mLifecycleOwner;
61 
62     @Mock
63     LiveData<Collection<ComplicationViewModel>> mComplicationViewModelLiveData;
64 
65     @Mock
66     ComplicationCollectionViewModel mViewModel;
67 
68     @Mock
69     ComplicationViewModel mComplicationViewModel;
70 
71     @Mock
72     ComplicationLayoutEngine mLayoutEngine;
73 
74     @Mock
75     ComplicationId mComplicationId;
76 
77     @Mock
78     Complication mComplication;
79 
80     @Mock
81     Complication.ViewHolder mViewHolder;
82 
83     @Mock
84     View mComplicationView;
85 
86     @Mock
87     ComplicationLayoutParams mComplicationLayoutParams;
88 
89     @Mock
90     DreamOverlayStateController mDreamOverlayStateController;
91 
92     @Captor
93     private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
94 
95     @Complication.Category
96     static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
97 
98     private ComplicationHostViewController mController;
99 
100     private SecureSettings mSecureSettings;
101 
102     private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
103 
104     @Before
setup()105     public void setup() {
106         MockitoAnnotations.initMocks(this);
107         when(mViewModel.getComplications()).thenReturn(mComplicationViewModelLiveData);
108         when(mComplicationViewModel.getId()).thenReturn(mComplicationId);
109         when(mComplicationViewModel.getComplication()).thenReturn(mComplication);
110         when(mComplication.createView(eq(mComplicationViewModel))).thenReturn(mViewHolder);
111         when(mViewHolder.getView()).thenReturn(mComplicationView);
112         when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
113         when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
114         when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
115 
116         mSecureSettings = new FakeSettings();
117         mSecureSettings.putFloatForUser(
118                         Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID);
119 
120         mController = new ComplicationHostViewController(
121                 mComplicationHostView,
122                 mLayoutEngine,
123                 mDreamOverlayStateController,
124                 mLifecycleOwner,
125                 mViewModel,
126                 mSecureSettings);
127 
128         mController.init();
129     }
130 
131     /**
132      * Ensures the lifecycle of complications is properly handled.
133      */
134     @Test
testViewModelObservation()135     public void testViewModelObservation() {
136         final Observer<Collection<ComplicationViewModel>> observer =
137                 captureComplicationViewModelsObserver();
138 
139         // Add a complication and ensure it is added to the view.
140         final HashSet<ComplicationViewModel> complications = new HashSet<>(
141                 Collections.singletonList(mComplicationViewModel));
142         observer.onChanged(complications);
143 
144         verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
145                 eq(mComplicationLayoutParams), eq(COMPLICATION_CATEGORY));
146 
147         // Remove complication and ensure it is removed from the view by id.
148         observer.onChanged(new HashSet<>());
149 
150         verify(mLayoutEngine).removeComplication(eq(mComplicationId));
151     }
152 
153     @Test
testMalformedComplicationAddition()154     public void testMalformedComplicationAddition() {
155         final Observer<Collection<ComplicationViewModel>> observer =
156                 captureComplicationViewModelsObserver();
157 
158         // Add a complication and ensure it is added to the view.
159         final HashSet<ComplicationViewModel> complications = new HashSet<>(
160                 Collections.singletonList(mComplicationViewModel));
161         when(mViewHolder.getView()).thenReturn(null);
162         observer.onChanged(complications);
163 
164         verify(mLayoutEngine, never()).addComplication(any(), any(), any(), anyInt());
165 
166     }
167 
168     @Test
testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible()169     public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
170         final Observer<Collection<ComplicationViewModel>> observer =
171                 captureComplicationViewModelsObserver();
172 
173         // Add a complication before entry animations are finished.
174         final HashSet<ComplicationViewModel> complications = new HashSet<>(
175                 Collections.singletonList(mComplicationViewModel));
176         observer.onChanged(complications);
177 
178         // The complication view should be set to invisible.
179         verify(mComplicationView).setVisibility(View.INVISIBLE);
180     }
181 
182     @Test
testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible()183     public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
184         final Observer<Collection<ComplicationViewModel>> observer =
185                 captureComplicationViewModelsObserver();
186 
187         // Dream entry animations finished.
188         when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
189 
190         // Add a complication after entry animations are finished.
191         final HashSet<ComplicationViewModel> complications = new HashSet<>(
192                 Collections.singletonList(mComplicationViewModel));
193         observer.onChanged(complications);
194 
195         // The complication view should not be set to invisible.
196         verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
197     }
198 
199     @Test
testAnimationsDisabled_ComplicationsNeverSetToInvisible()200     public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() {
201         //Disable animations
202         mController.mIsAnimationEnabled = false;
203 
204         final Observer<Collection<ComplicationViewModel>> observer =
205                 captureComplicationViewModelsObserver();
206 
207         // Add a complication before entry animations are finished.
208         final HashSet<ComplicationViewModel> complications = new HashSet<>(
209                 Collections.singletonList(mComplicationViewModel));
210         observer.onChanged(complications);
211 
212         // The complication view should not be set to invisible.
213         verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
214     }
215 
captureComplicationViewModelsObserver()216     private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
217         verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
218                 mObserverCaptor.capture());
219         return mObserverCaptor.getValue();
220     }
221 }
222