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