1 /* 2 * Copyright (C) 2020 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.documentsui.sidebar; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.app.admin.DevicePolicyManager; 22 import android.content.res.Resources; 23 import android.view.View; 24 25 import androidx.test.filters.MediumTest; 26 import androidx.test.platform.app.InstrumentationRegistry; 27 import androidx.test.runner.AndroidJUnit4; 28 29 import com.android.documentsui.R; 30 import com.android.documentsui.TestConfigStore; 31 import com.android.documentsui.base.State; 32 import com.android.documentsui.base.UserId; 33 import com.android.documentsui.testing.TestProvidersAccess; 34 import com.android.modules.utils.build.SdkLevel; 35 36 import com.google.common.collect.Lists; 37 import com.google.common.truth.Correspondence; 38 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.HashMap; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Objects; 49 50 /** 51 * A unit test for {@link UserItemsCombiner}. 52 */ 53 @RunWith(AndroidJUnit4.class) 54 @MediumTest 55 public class UserItemsCombinerTest { 56 private static final UserId PERSONAL_USER = TestProvidersAccess.USER_ID; 57 private static final UserId WORK_USER = TestProvidersAccess.OtherUser.USER_ID; 58 private static final UserId PRIVATE_USER = TestProvidersAccess.AnotherUser.USER_ID; 59 60 private static final List<Item> PERSONAL_ITEMS = Lists.newArrayList( 61 personalItem("personal 1"), 62 personalItem("personal 2"), 63 personalItem("personal 3") 64 ); 65 66 private static final List<Item> WORK_ITEMS = Lists.newArrayList( 67 workItem("work 1") 68 ); 69 70 private static final List<Item> PRIVATE_ITEMS = Lists.newArrayList( 71 privateItem("private1") 72 ); 73 74 private static final Correspondence<Item, Item> ITEM_CORRESPONDENCE = 75 Correspondence.from((Item actual, Item expected) -> { 76 return Objects.equals(actual.title, expected.title) 77 && Objects.equals(actual.userId, expected.userId); 78 }, "has same title and userId as in"); 79 80 private final State mState = new State(); 81 private final Resources mResources = 82 InstrumentationRegistry.getInstrumentation().getTargetContext().getResources(); 83 private final DevicePolicyManager mDpm = 84 InstrumentationRegistry.getInstrumentation().getTargetContext().getSystemService( 85 DevicePolicyManager.class); 86 private final List<UserId> mUserIds = new ArrayList<>(); 87 private final Map<UserId, String> mUserIdToLabelMap = new HashMap<>(); 88 private final TestConfigStore mTestConfigStore = new TestConfigStore(); 89 private UserItemsCombiner mCombiner; 90 91 @Before setUp()92 public void setUp() { 93 mState.canShareAcrossProfile = true; 94 mState.supportsCrossProfile = true; 95 mUserIds.add(PERSONAL_USER); 96 mUserIds.add(WORK_USER); 97 mUserIdToLabelMap.put(PERSONAL_USER, "Personal"); 98 mUserIdToLabelMap.put(WORK_USER, "Work"); 99 mState.canForwardToProfileIdMap.put(PERSONAL_USER, true); 100 mState.canForwardToProfileIdMap.put(WORK_USER, true); 101 mState.configStore = mTestConfigStore; 102 103 if (SdkLevel.isAtLeastV()) { 104 mUserIds.add(PRIVATE_USER); 105 mUserIdToLabelMap.put(PRIVATE_USER, "Private"); 106 mState.canForwardToProfileIdMap.put(PRIVATE_USER, true); 107 mTestConfigStore.enablePrivateSpaceInPhotoPicker(); 108 } 109 } 110 111 @Test testCreatePresentableList_empty()112 public void testCreatePresentableList_empty() { 113 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 114 .setRootListForCurrentUser(Collections.emptyList()) 115 .setRootListForOtherUser(Collections.emptyList()); 116 assertThat(mCombiner.createPresentableList()).isEmpty(); 117 } 118 119 @Test testCreatePresentableListForAllUsers_empty()120 public void testCreatePresentableListForAllUsers_empty() { 121 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 122 rootListAllUsers.add(Collections.emptyList()); 123 rootListAllUsers.add(Collections.emptyList()); 124 if (SdkLevel.isAtLeastV()) { 125 rootListAllUsers.add(Collections.emptyList()); 126 } 127 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 128 .setRootListForAllUsers(rootListAllUsers); 129 assertThat( 130 mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)).isEmpty(); 131 } 132 133 @Test testCreatePresentableList_currentIsPersonal_personalItemsOnly()134 public void testCreatePresentableList_currentIsPersonal_personalItemsOnly() { 135 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 136 .setRootListForCurrentUser(PERSONAL_ITEMS) 137 .setRootListForOtherUser(Collections.emptyList()); 138 assertThat(mCombiner.createPresentableList()) 139 .comparingElementsUsing(ITEM_CORRESPONDENCE) 140 .containsExactlyElementsIn(PERSONAL_ITEMS) 141 .inOrder(); 142 } 143 144 @Test testCreatePresentableList_currentIsWork_personalItemsOnly()145 public void testCreatePresentableList_currentIsWork_personalItemsOnly() { 146 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 147 .overrideCurrentUserForTest(WORK_USER) 148 .setRootListForCurrentUser(Collections.emptyList()) 149 .setRootListForOtherUser(PERSONAL_ITEMS); 150 assertThat(mCombiner.createPresentableList()) 151 .comparingElementsUsing(ITEM_CORRESPONDENCE) 152 .containsExactlyElementsIn(PERSONAL_ITEMS) 153 .inOrder(); 154 } 155 156 @Test testCreatePresentableListForAllUsers_currentIsPersonal_personalItemsOnly()157 public void testCreatePresentableListForAllUsers_currentIsPersonal_personalItemsOnly() { 158 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 159 rootListAllUsers.add(Lists.newArrayList(PERSONAL_ITEMS)); 160 rootListAllUsers.add(Collections.emptyList()); 161 if (SdkLevel.isAtLeastV()) { 162 rootListAllUsers.add(Collections.emptyList()); 163 } 164 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 165 .setRootListForAllUsers(rootListAllUsers); 166 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 167 .comparingElementsUsing(ITEM_CORRESPONDENCE) 168 .containsExactlyElementsIn(PERSONAL_ITEMS) 169 .inOrder(); 170 } 171 172 @Test testCreatePresentableListForAllUsers_currentIsWork_personalItemsOnly()173 public void testCreatePresentableListForAllUsers_currentIsWork_personalItemsOnly() { 174 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 175 rootListAllUsers.add(Lists.newArrayList(PERSONAL_ITEMS)); 176 rootListAllUsers.add(Collections.emptyList()); 177 if (SdkLevel.isAtLeastV()) { 178 rootListAllUsers.add(Collections.emptyList()); 179 } 180 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 181 .overrideCurrentUserForTest(WORK_USER) 182 .setRootListForAllUsers(rootListAllUsers); 183 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 184 .comparingElementsUsing(ITEM_CORRESPONDENCE) 185 .containsExactlyElementsIn(PERSONAL_ITEMS) 186 .inOrder(); 187 } 188 189 @Test testCreatePresentableListForAllUsers_currentIsPrivate_personalItemsOnly()190 public void testCreatePresentableListForAllUsers_currentIsPrivate_personalItemsOnly() { 191 if (!SdkLevel.isAtLeastV()) return; 192 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 193 rootListAllUsers.add(Lists.newArrayList(PERSONAL_ITEMS)); 194 rootListAllUsers.add(Collections.emptyList()); 195 rootListAllUsers.add(Collections.emptyList()); 196 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 197 .overrideCurrentUserForTest(PRIVATE_USER) 198 .setRootListForAllUsers(rootListAllUsers); 199 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 200 .comparingElementsUsing(ITEM_CORRESPONDENCE) 201 .containsExactlyElementsIn(PERSONAL_ITEMS) 202 .inOrder(); 203 } 204 205 @Test testCreatePresentableList_currentIsPersonal_workItemsOnly()206 public void testCreatePresentableList_currentIsPersonal_workItemsOnly() { 207 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 208 .setRootListForCurrentUser(Collections.emptyList()) 209 .setRootListForOtherUser(WORK_ITEMS); 210 assertThat(mCombiner.createPresentableList()) 211 .comparingElementsUsing(ITEM_CORRESPONDENCE) 212 .containsExactlyElementsIn(WORK_ITEMS) 213 .inOrder(); 214 } 215 216 @Test testCreatePresentableList_currentIsWork_workItemsOnly()217 public void testCreatePresentableList_currentIsWork_workItemsOnly() { 218 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 219 .overrideCurrentUserForTest(WORK_USER) 220 .setRootListForCurrentUser(WORK_ITEMS) 221 .setRootListForOtherUser(Collections.emptyList()); 222 assertThat(mCombiner.createPresentableList()) 223 .comparingElementsUsing(ITEM_CORRESPONDENCE) 224 .containsExactlyElementsIn(WORK_ITEMS) 225 .inOrder(); 226 } 227 228 @Test testCreatePresentableListForAllUsers_currentIsPersonal_workItemsOnly()229 public void testCreatePresentableListForAllUsers_currentIsPersonal_workItemsOnly() { 230 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 231 rootListAllUsers.add(Collections.emptyList()); 232 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 233 if (SdkLevel.isAtLeastV()) { 234 rootListAllUsers.add(Collections.emptyList()); 235 } 236 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 237 .overrideCurrentUserForTest(PERSONAL_USER) 238 .setRootListForAllUsers(rootListAllUsers); 239 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 240 .comparingElementsUsing(ITEM_CORRESPONDENCE) 241 .containsExactlyElementsIn(WORK_ITEMS) 242 .inOrder(); 243 } 244 245 @Test testCreatePresentableListForAllUsers_currentIsWork_workItemsOnly()246 public void testCreatePresentableListForAllUsers_currentIsWork_workItemsOnly() { 247 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 248 rootListAllUsers.add(Collections.emptyList()); 249 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 250 if (SdkLevel.isAtLeastV()) { 251 rootListAllUsers.add(Collections.emptyList()); 252 } 253 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 254 .overrideCurrentUserForTest(WORK_USER) 255 .setRootListForAllUsers(rootListAllUsers); 256 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 257 .comparingElementsUsing(ITEM_CORRESPONDENCE) 258 .containsExactlyElementsIn(WORK_ITEMS) 259 .inOrder(); 260 } 261 262 @Test testCreatePresentableListForAllUsers_currentIsPrivate_workItemsOnly()263 public void testCreatePresentableListForAllUsers_currentIsPrivate_workItemsOnly() { 264 if (!SdkLevel.isAtLeastV()) return; 265 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 266 rootListAllUsers.add(Collections.emptyList()); 267 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 268 rootListAllUsers.add(Collections.emptyList()); 269 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 270 .overrideCurrentUserForTest(PRIVATE_USER) 271 .setRootListForAllUsers(rootListAllUsers); 272 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 273 .comparingElementsUsing(ITEM_CORRESPONDENCE) 274 .containsExactlyElementsIn(WORK_ITEMS) 275 .inOrder(); 276 } 277 278 @Test testCreatePresentableList_currentIsPersonal_personalAndWorkItems()279 public void testCreatePresentableList_currentIsPersonal_personalAndWorkItems() { 280 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 281 .setRootListForCurrentUser(PERSONAL_ITEMS) 282 .setRootListForOtherUser(WORK_ITEMS); 283 284 List<Item> expected = Lists.newArrayList(); 285 expected.add(new HeaderItem(mResources.getString(R.string.personal_tab))); 286 expected.addAll(PERSONAL_ITEMS); 287 expected.add(new HeaderItem(mResources.getString(R.string.work_tab))); 288 expected.addAll(WORK_ITEMS); 289 290 assertThat(mCombiner.createPresentableList()) 291 .comparingElementsUsing(ITEM_CORRESPONDENCE) 292 .containsExactlyElementsIn(expected) 293 .inOrder(); 294 } 295 296 @Test testCreatePresentableList_currentIsWork_personalAndWorkItems()297 public void testCreatePresentableList_currentIsWork_personalAndWorkItems() { 298 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 299 .overrideCurrentUserForTest(WORK_USER) 300 .setRootListForCurrentUser(WORK_ITEMS) 301 .setRootListForOtherUser(PERSONAL_ITEMS); 302 303 List<Item> expected = Lists.newArrayList(); 304 expected.add(new HeaderItem(mResources.getString(R.string.personal_tab))); 305 expected.addAll(PERSONAL_ITEMS); 306 expected.add(new HeaderItem(mResources.getString(R.string.work_tab))); 307 expected.addAll(WORK_ITEMS); 308 309 assertThat(mCombiner.createPresentableList()) 310 .comparingElementsUsing(ITEM_CORRESPONDENCE) 311 .containsExactlyElementsIn(expected) 312 .inOrder(); 313 } 314 315 @Test testCreatePresentableListForAllUsers_currentIsPersonal_allUsersItems()316 public void testCreatePresentableListForAllUsers_currentIsPersonal_allUsersItems() { 317 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 318 rootListAllUsers.add(PERSONAL_ITEMS); 319 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 320 if (SdkLevel.isAtLeastV()) { 321 rootListAllUsers.add(PRIVATE_ITEMS); 322 } 323 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 324 .setRootListForAllUsers(rootListAllUsers); 325 326 List<Item> expected = Lists.newArrayList(); 327 expected.add(new HeaderItem("Personal")); 328 expected.addAll(PERSONAL_ITEMS); 329 expected.add(new HeaderItem("Work")); 330 expected.addAll(WORK_ITEMS); 331 if (SdkLevel.isAtLeastV()) { 332 expected.add(new HeaderItem("Private")); 333 expected.addAll(PRIVATE_ITEMS); 334 } 335 336 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 337 .comparingElementsUsing(ITEM_CORRESPONDENCE) 338 .containsExactlyElementsIn(expected) 339 .inOrder(); 340 } 341 342 @Test testCreatePresentableListForAllUsers_currentIsWork_allUsersItems()343 public void testCreatePresentableListForAllUsers_currentIsWork_allUsersItems() { 344 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 345 rootListAllUsers.add(PERSONAL_ITEMS); 346 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 347 if (SdkLevel.isAtLeastV()) { 348 rootListAllUsers.add(PRIVATE_ITEMS); 349 } 350 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 351 .overrideCurrentUserForTest(WORK_USER) 352 .setRootListForAllUsers(rootListAllUsers); 353 354 List<Item> expected = Lists.newArrayList(); 355 expected.add(new HeaderItem("Personal")); 356 expected.addAll(PERSONAL_ITEMS); 357 expected.add(new HeaderItem("Work")); 358 expected.addAll(WORK_ITEMS); 359 if (SdkLevel.isAtLeastV()) { 360 expected.add(new HeaderItem("Private")); 361 expected.addAll(PRIVATE_ITEMS); 362 } 363 364 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 365 .comparingElementsUsing(ITEM_CORRESPONDENCE) 366 .containsExactlyElementsIn(expected) 367 .inOrder(); 368 } 369 370 @Test testCreatePresentableListForAllUsers_currentIsPrivate_allUsersItems()371 public void testCreatePresentableListForAllUsers_currentIsPrivate_allUsersItems() { 372 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 373 rootListAllUsers.add(PERSONAL_ITEMS); 374 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 375 if (SdkLevel.isAtLeastV()) { 376 rootListAllUsers.add(PRIVATE_ITEMS); 377 } 378 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 379 .overrideCurrentUserForTest(PRIVATE_USER) 380 .setRootListForAllUsers(rootListAllUsers); 381 382 List<Item> expected = Lists.newArrayList(); 383 expected.add(new HeaderItem("Personal")); 384 expected.addAll(PERSONAL_ITEMS); 385 expected.add(new HeaderItem("Work")); 386 expected.addAll(WORK_ITEMS); 387 if (SdkLevel.isAtLeastV()) { 388 expected.add(new HeaderItem("Private")); 389 expected.addAll(PRIVATE_ITEMS); 390 } 391 392 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 393 .comparingElementsUsing(ITEM_CORRESPONDENCE) 394 .containsExactlyElementsIn(expected) 395 .inOrder(); 396 } 397 398 @Test testCreatePresentableList_currentIsPersonal_personalAndWorkItems_cannotShare()399 public void testCreatePresentableList_currentIsPersonal_personalAndWorkItems_cannotShare() { 400 mState.canShareAcrossProfile = false; 401 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 402 .setRootListForCurrentUser(PERSONAL_ITEMS) 403 .setRootListForOtherUser(WORK_ITEMS); 404 405 assertThat(mCombiner.createPresentableList()) 406 .comparingElementsUsing(ITEM_CORRESPONDENCE) 407 .containsExactlyElementsIn(PERSONAL_ITEMS) 408 .inOrder(); 409 } 410 411 @Test testCreatePresentableList_currentIsWork_personalItemsOnly_cannotShare()412 public void testCreatePresentableList_currentIsWork_personalItemsOnly_cannotShare() { 413 mState.canShareAcrossProfile = false; 414 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 415 .overrideCurrentUserForTest(WORK_USER) 416 .setRootListForCurrentUser(Collections.emptyList()) 417 .setRootListForOtherUser(PERSONAL_ITEMS); 418 419 assertThat(mCombiner.createPresentableList()).isEmpty(); 420 } 421 422 @Test testCreatePresentableListForAllUsers_currentIsPersonal_cannotShareToWork()423 public void testCreatePresentableListForAllUsers_currentIsPersonal_cannotShareToWork() { 424 if (!SdkLevel.isAtLeastV()) return; 425 mState.canForwardToProfileIdMap.put(WORK_USER, false); 426 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 427 rootListAllUsers.add(PERSONAL_ITEMS); 428 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 429 if (SdkLevel.isAtLeastV()) { 430 rootListAllUsers.add(PRIVATE_ITEMS); 431 } 432 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 433 .setRootListForAllUsers(rootListAllUsers); 434 435 List<Item> expected = Lists.newArrayList(); 436 expected.add(new HeaderItem("Personal")); 437 expected.addAll(PERSONAL_ITEMS); 438 if (SdkLevel.isAtLeastV()) { 439 expected.add(new HeaderItem("Private")); 440 expected.addAll(PRIVATE_ITEMS); 441 } 442 443 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 444 .comparingElementsUsing(ITEM_CORRESPONDENCE) 445 .containsExactlyElementsIn(expected) 446 .inOrder(); 447 } 448 449 @Test testCreatePresentableListForAllUsers_currentIsWork_AllItems_cannotSharePersonal()450 public void testCreatePresentableListForAllUsers_currentIsWork_AllItems_cannotSharePersonal() { 451 if (!SdkLevel.isAtLeastV()) return; 452 mState.canForwardToProfileIdMap.put(PERSONAL_USER, false); 453 // In the current implementation of cross profile content sharing strategy work profile will 454 // be able to share to all the child profiles of the parent/personal profile only if it is 455 // able to share with parent/personal profile 456 mState.canForwardToProfileIdMap.put(PRIVATE_USER, false); 457 final List<List<Item>> rootListAllUsers = new ArrayList<>(); 458 rootListAllUsers.add(PERSONAL_ITEMS); 459 rootListAllUsers.add(Lists.newArrayList(WORK_ITEMS)); 460 if (SdkLevel.isAtLeastV()) { 461 rootListAllUsers.add(PRIVATE_ITEMS); 462 } 463 mCombiner = new UserItemsCombiner(mResources, mDpm, mState) 464 .overrideCurrentUserForTest(WORK_USER) 465 .setRootListForAllUsers(rootListAllUsers); 466 467 assertThat(mCombiner.createPresentableListForAllUsers(mUserIds, mUserIdToLabelMap)) 468 .comparingElementsUsing(ITEM_CORRESPONDENCE) 469 .containsExactlyElementsIn(WORK_ITEMS) 470 .inOrder(); 471 } 472 personalItem(String title)473 private static TestItem personalItem(String title) { 474 return new TestItem(title, PERSONAL_USER); 475 } 476 workItem(String title)477 private static TestItem workItem(String title) { 478 return new TestItem(title, WORK_USER); 479 } 480 privateItem(String title)481 private static TestItem privateItem(String title) { 482 return new TestItem(title, PRIVATE_USER); 483 } 484 485 private static class TestItem extends Item { 486 TestItem(String title, UserId userId)487 TestItem(String title, UserId userId) { 488 super(/* layoutId= */ 0, title, /* stringId= */ "", userId); 489 } 490 491 @Override bindView(View convertView)492 void bindView(View convertView) { 493 } 494 495 @Override isRoot()496 boolean isRoot() { 497 return false; 498 } 499 500 @Override open()501 void open() { 502 } 503 504 @Override toString()505 public String toString() { 506 return title + "(" + userId + ")"; 507 } 508 } 509 } 510