1 /* 2 * Copyright (C) 2016 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.settings.widget; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.Mockito.mock; 25 import static org.mockito.Mockito.never; 26 import static org.mockito.Mockito.verify; 27 import static org.mockito.Mockito.when; 28 29 import android.app.ActionBar; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.ActivityInfo; 33 import android.content.pm.ApplicationInfo; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.ResolveInfo; 36 import android.graphics.drawable.ColorDrawable; 37 import android.os.UserHandle; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.widget.ImageButton; 41 import android.widget.ImageView; 42 import android.widget.TextView; 43 44 import androidx.fragment.app.Fragment; 45 import androidx.fragment.app.FragmentActivity; 46 import androidx.preference.Preference; 47 48 import com.android.settings.R; 49 import com.android.settingslib.applications.ApplicationsState; 50 import com.android.settingslib.widget.LayoutPreference; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.Answers; 56 import org.mockito.Mock; 57 import org.mockito.MockitoAnnotations; 58 import org.robolectric.RobolectricTestRunner; 59 import org.robolectric.RuntimeEnvironment; 60 61 @RunWith(RobolectricTestRunner.class) 62 public class EntityHeaderControllerTest { 63 64 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 65 private Context mContext; 66 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 67 private FragmentActivity mActivity; 68 @Mock 69 private Fragment mFragment; 70 71 private Context mShadowContext; 72 private LayoutInflater mLayoutInflater; 73 private PackageInfo mInfo; 74 private EntityHeaderController mController; 75 76 @Before setUp()77 public void setUp() { 78 MockitoAnnotations.initMocks(this); 79 mShadowContext = RuntimeEnvironment.application; 80 when(mActivity.getApplicationContext()).thenReturn(mShadowContext); 81 when(mContext.getApplicationContext()).thenReturn(mContext); 82 when(mFragment.getContext()).thenReturn(mShadowContext); 83 mLayoutInflater = LayoutInflater.from(mShadowContext); 84 mInfo = new PackageInfo(); 85 mInfo.versionName = "1234"; 86 } 87 88 @Test testBuildView_constructedWithoutView_shouldCreateNewView()89 public void testBuildView_constructedWithoutView_shouldCreateNewView() { 90 mController = EntityHeaderController.newInstance(mActivity, mFragment, null); 91 View view = mController.done(mActivity); 92 93 assertThat(view).isNotNull(); 94 } 95 96 @Test testBuildView_withContext_shouldBuildPreferenceAllowedBelowDivider()97 public void testBuildView_withContext_shouldBuildPreferenceAllowedBelowDivider() { 98 mController = EntityHeaderController.newInstance(mActivity, mFragment, null); 99 Preference preference = mController.done(mActivity, mShadowContext); 100 101 assertThat(preference instanceof LayoutPreference).isTrue(); 102 assertThat(((LayoutPreference)preference).isAllowDividerBelow()).isTrue(); 103 } 104 105 @Test testBuildView_constructedWithView_shouldReturnSameView()106 public void testBuildView_constructedWithView_shouldReturnSameView() { 107 View inputView = mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 108 mController = EntityHeaderController.newInstance(mActivity, mFragment, inputView); 109 View view = mController.done(mActivity); 110 111 assertThat(view).isSameAs(inputView); 112 } 113 114 @Test bindViews_shouldBindAllData()115 public void bindViews_shouldBindAllData() { 116 final String testString = "test"; 117 final View header = 118 mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 119 final TextView label = header.findViewById(R.id.entity_header_title); 120 final TextView summary = header.findViewById(R.id.entity_header_summary); 121 final TextView secondSummary = header.findViewById(R.id.entity_header_second_summary); 122 123 mController = EntityHeaderController.newInstance(mActivity, mFragment, header); 124 mController.setLabel(testString); 125 mController.setSummary(testString); 126 mController.setSecondSummary(testString); 127 mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add_24dp)); 128 mController.done(mActivity); 129 130 assertThat(label).isNotNull(); 131 assertThat(label.getText()).isEqualTo(testString); 132 assertThat(summary).isNotNull(); 133 assertThat(summary.getText()).isEqualTo(testString); 134 assertThat(secondSummary).isNotNull(); 135 assertThat(secondSummary.getText()).isEqualTo(testString); 136 } 137 138 @Test bindButton_hasEditClickListener_shouldShowButton()139 public void bindButton_hasEditClickListener_shouldShowButton() { 140 final ResolveInfo info = new ResolveInfo(); 141 info.activityInfo = new ActivityInfo(); 142 info.activityInfo.packageName = "123"; 143 info.activityInfo.name = "321"; 144 final View view = mLayoutInflater 145 .inflate(R.layout.settings_entity_header, null /* root */); 146 when(mActivity.getApplicationContext()).thenReturn(mContext); 147 148 mController = EntityHeaderController.newInstance(mActivity, mFragment, view); 149 mController.setEditListener(new View.OnClickListener() { 150 public void onClick(View v) { 151 // do nothing 152 } 153 }); 154 mController.setButtonActions( 155 EntityHeaderController.ActionType.ACTION_EDIT_PREFERENCE, 156 EntityHeaderController.ActionType.ACTION_NONE); 157 mController.done(mActivity); 158 159 final ImageButton button1 = view.findViewById(android.R.id.button1); 160 assertThat(button1).isNotNull(); 161 assertThat(button1.getVisibility()).isEqualTo(View.VISIBLE); 162 assertThat(button1.getDrawable()).isNotNull(); 163 assertThat(view.findViewById(android.R.id.button2).getVisibility()).isEqualTo(View.GONE); 164 } 165 166 @Test bindButton_noEditClickListener_shouldNotShowButton()167 public void bindButton_noEditClickListener_shouldNotShowButton() { 168 final ResolveInfo info = new ResolveInfo(); 169 info.activityInfo = new ActivityInfo(); 170 info.activityInfo.packageName = "123"; 171 info.activityInfo.name = "321"; 172 final View view = mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 173 when(mActivity.getApplicationContext()).thenReturn(mContext); 174 175 mController = EntityHeaderController.newInstance(mActivity, mFragment, view); 176 mController.setButtonActions( 177 EntityHeaderController.ActionType.ACTION_EDIT_PREFERENCE, 178 EntityHeaderController.ActionType.ACTION_NONE); 179 mController.done(mActivity); 180 181 assertThat(view.findViewById(android.R.id.button1).getVisibility()).isEqualTo(View.GONE); 182 assertThat(view.findViewById(android.R.id.button2).getVisibility()).isEqualTo(View.GONE); 183 } 184 185 186 @Test bindButton_noAppInfo_shouldNotAttachClickListener()187 public void bindButton_noAppInfo_shouldNotAttachClickListener() { 188 final View appLinks = 189 mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 190 final FragmentActivity activity = mock(FragmentActivity.class); 191 when(mFragment.getActivity()).thenReturn(activity); 192 193 mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks); 194 mController.setPackageName(null) 195 .setHasAppInfoLink(true) 196 .setButtonActions( 197 EntityHeaderController.ActionType.ACTION_NONE, 198 EntityHeaderController.ActionType.ACTION_NONE); 199 mController.done(mActivity); 200 201 assertThat(appLinks.findViewById(android.R.id.button1).getVisibility()) 202 .isEqualTo(View.GONE); 203 assertThat(appLinks.findViewById(android.R.id.button2).getVisibility()) 204 .isEqualTo(View.GONE); 205 206 appLinks.findViewById(R.id.entity_header_content).performClick(); 207 verify(mFragment, never()).getActivity(); 208 verify(activity, never()).startActivity(any(Intent.class)); 209 } 210 211 @Test bindButton_hasAppInfo_shouldAttachClickListener()212 public void bindButton_hasAppInfo_shouldAttachClickListener() { 213 final View appLinks = 214 mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 215 final FragmentActivity activity = mock(FragmentActivity.class); 216 when(mFragment.getActivity()).thenReturn(activity); 217 when(mContext.getString(eq(R.string.application_info_label))).thenReturn("App Info"); 218 219 mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks); 220 mController.setPackageName("123") 221 .setUid(123321) 222 .setHasAppInfoLink(true) 223 .setButtonActions( 224 EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, 225 EntityHeaderController.ActionType.ACTION_NONE); 226 mController.done(mActivity); 227 228 appLinks.findViewById(R.id.entity_header_content).performClick(); 229 verify(activity) 230 .startActivityForResultAsUser(any(Intent.class), anyInt(), any(UserHandle.class)); 231 } 232 233 @Test iconContentDescription_shouldWorkWithSetIcon()234 public void iconContentDescription_shouldWorkWithSetIcon() { 235 final View view = 236 mLayoutInflater.inflate(R.layout.settings_entity_header, null /* root */); 237 when(mFragment.getActivity()).thenReturn(mock(FragmentActivity.class)); 238 mController = EntityHeaderController.newInstance(mActivity, mFragment, view); 239 String description = "Fake Description"; 240 mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add_24dp)); 241 mController.setIconContentDescription(description); 242 mController.done(mActivity); 243 assertThat(view.findViewById(R.id.entity_header_icon).getContentDescription().toString()) 244 .isEqualTo(description); 245 } 246 247 @Test iconContentDescription_shouldWorkWithoutSetIcon()248 public void iconContentDescription_shouldWorkWithoutSetIcon() { 249 final View view = mLayoutInflater 250 .inflate(R.layout.settings_entity_header, null /* root */); 251 when(mFragment.getActivity()).thenReturn(mock(FragmentActivity.class)); 252 mController = EntityHeaderController.newInstance(mActivity, mFragment, view); 253 String description = "Fake Description"; 254 mController.setIconContentDescription(description); 255 mController.done(mActivity); 256 assertThat(view.findViewById(R.id.entity_header_icon).getContentDescription().toString()) 257 .isEqualTo(description); 258 } 259 260 @Test setIcon_usingAppEntry_shouldLoadIconFromDrawableFactory()261 public void setIcon_usingAppEntry_shouldLoadIconFromDrawableFactory() { 262 final View view = mLayoutInflater 263 .inflate(R.layout.settings_entity_header, null /* root */); 264 final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); 265 entry.info = new ApplicationInfo(); 266 mController = EntityHeaderController.newInstance(mActivity, mFragment, view); 267 mController.setIcon(entry).done(mActivity); 268 final ImageView iconView = view.findViewById(R.id.entity_header_icon); 269 270 // ... entry.icon is still empty. This means the icon didn't come from cache. 271 assertThat(entry.icon).isNull(); 272 } 273 274 @Test bindButton_hasAppNotifIntent_shouldShowButton()275 public void bindButton_hasAppNotifIntent_shouldShowButton() { 276 final View appLinks = mLayoutInflater 277 .inflate(R.layout.settings_entity_header, null /* root */); 278 279 mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks); 280 mController.setAppNotifPrefIntent(new Intent()) 281 .setButtonActions( 282 EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, 283 EntityHeaderController.ActionType.ACTION_NONE); 284 mController.done(mActivity); 285 286 assertThat(appLinks.findViewById(android.R.id.button1).getVisibility()) 287 .isEqualTo(View.VISIBLE); 288 assertThat(appLinks.findViewById(android.R.id.button2).getVisibility()) 289 .isEqualTo(View.GONE); 290 } 291 292 // Ensure that the instant app label does not show up when we haven't told the controller the 293 // app is instant. 294 @Test instantApps_normalAppsDontGetLabel()295 public void instantApps_normalAppsDontGetLabel() { 296 final View header = mLayoutInflater.inflate( 297 R.layout.settings_entity_header, null /* root */); 298 mController = EntityHeaderController.newInstance(mActivity, mFragment, header); 299 mController.done(mActivity); 300 301 assertThat(header.findViewById(R.id.install_type).getVisibility()) 302 .isEqualTo(View.GONE); 303 } 304 305 // Test that the "instant apps" label is present in the header when we have an instant app. 306 @Test instantApps_expectedHeaderItem()307 public void instantApps_expectedHeaderItem() { 308 final View header = mLayoutInflater.inflate( 309 R.layout.settings_entity_header, null /* root */); 310 mController = EntityHeaderController.newInstance(mActivity, mFragment, header); 311 mController.setIsInstantApp(true); 312 mController.done(mActivity); 313 TextView label = header.findViewById(R.id.install_type); 314 315 assertThat(label.getVisibility()).isEqualTo(View.VISIBLE); 316 assertThat(label.getText()).isEqualTo( 317 header.getResources().getString(R.string.install_type_instant)); 318 assertThat(header.findViewById(R.id.entity_header_summary).getVisibility()) 319 .isEqualTo(View.GONE); 320 } 321 322 @Test styleActionBar_invalidObjects_shouldNotCrash()323 public void styleActionBar_invalidObjects_shouldNotCrash() { 324 mController = EntityHeaderController.newInstance(mActivity, mFragment, null); 325 mController.styleActionBar(null); 326 327 when(mActivity.getActionBar()).thenReturn(null); 328 mController.styleActionBar(mActivity); 329 330 verify(mActivity).getActionBar(); 331 } 332 333 @Test styleActionBar_setElevationAndBackground()334 public void styleActionBar_setElevationAndBackground() { 335 final ActionBar actionBar = mActivity.getActionBar(); 336 337 mController = EntityHeaderController.newInstance(mActivity, mFragment, null); 338 mController.styleActionBar(mActivity); 339 340 verify(actionBar).setElevation(0); 341 // Enforce a color drawable as background here, as image based drawables might not be 342 // wide enough to cover entire action bar. 343 verify(actionBar).setBackgroundDrawable(any(ColorDrawable.class)); 344 } 345 346 @Test initAppHeaderController_appHeaderNull_useFragmentContext()347 public void initAppHeaderController_appHeaderNull_useFragmentContext() { 348 mController = EntityHeaderController.newInstance(mActivity, mFragment, null); 349 350 // Fragment.getContext() is invoked to inflate the view 351 verify(mFragment).getContext(); 352 } 353 } 354