1 /* 2 * Copyright (C) 2008 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.launcher3; 18 19 import android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.app.ActivityManager; 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.graphics.Rect; 27 import android.os.Bundle; 28 import android.os.UserManager; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.accessibility.AccessibilityManager; 33 import android.widget.TextView; 34 35 class LauncherClings { 36 private static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed"; 37 private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed"; 38 private static final String MIGRATION_WORKSPACE_CLING_DISMISSED_KEY = 39 "cling_gel.migration_workspace.dismissed"; 40 private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed"; 41 private static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed"; 42 43 private static final boolean DISABLE_CLINGS = false; 44 45 private static final int SHOW_CLING_DURATION = 250; 46 private static final int DISMISS_CLING_DURATION = 200; 47 48 private Launcher mLauncher; 49 private LayoutInflater mInflater; 50 private HideFromAccessibilityHelper mHideFromAccessibilityHelper 51 = new HideFromAccessibilityHelper(); 52 53 /** Ctor */ 54 public LauncherClings(Launcher launcher) { 55 mLauncher = launcher; 56 mInflater = mLauncher.getLayoutInflater(); 57 } 58 59 /** Initializes a cling */ 60 private Cling initCling(int clingId, int scrimId, boolean animate, 61 boolean dimNavBarVisibilty) { 62 Cling cling = (Cling) mLauncher.findViewById(clingId); 63 View scrim = null; 64 if (scrimId > 0) { 65 scrim = mLauncher.findViewById(scrimId); 66 } 67 if (cling != null) { 68 cling.init(mLauncher, scrim); 69 cling.show(animate, SHOW_CLING_DURATION); 70 71 if (dimNavBarVisibilty) { 72 cling.setSystemUiVisibility(cling.getSystemUiVisibility() | 73 View.SYSTEM_UI_FLAG_LOW_PROFILE); 74 } 75 } 76 return cling; 77 } 78 79 /** Returns whether the clings are enabled or should be shown */ 80 private boolean areClingsEnabled() { 81 if (DISABLE_CLINGS) { 82 return false; 83 } 84 85 // disable clings when running in a test harness 86 if(ActivityManager.isRunningInTestHarness()) return false; 87 88 // Disable clings for accessibility when explore by touch is enabled 89 final AccessibilityManager a11yManager = (AccessibilityManager) mLauncher.getSystemService( 90 Launcher.ACCESSIBILITY_SERVICE); 91 if (a11yManager.isTouchExplorationEnabled()) { 92 return false; 93 } 94 95 // Restricted secondary users (child mode) will potentially have very few apps 96 // seeded when they start up for the first time. Clings won't work well with that 97 boolean supportsLimitedUsers = 98 android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 99 Account[] accounts = AccountManager.get(mLauncher).getAccounts(); 100 if (supportsLimitedUsers && accounts.length == 0) { 101 UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE); 102 Bundle restrictions = um.getUserRestrictions(); 103 if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { 104 return false; 105 } 106 } 107 return true; 108 } 109 110 /** Returns whether the folder cling is visible. */ 111 public boolean isFolderClingVisible() { 112 Cling cling = (Cling) mLauncher.findViewById(R.id.folder_cling); 113 if (cling != null) { 114 return cling.getVisibility() == View.VISIBLE; 115 } 116 return false; 117 } 118 119 private boolean skipCustomClingIfNoAccounts() { 120 Cling cling = (Cling) mLauncher.findViewById(R.id.workspace_cling); 121 boolean customCling = cling.getDrawIdentifier().equals("workspace_custom"); 122 if (customCling) { 123 AccountManager am = AccountManager.get(mLauncher); 124 if (am == null) return false; 125 Account[] accounts = am.getAccountsByType("com.google"); 126 return accounts.length == 0; 127 } 128 return false; 129 } 130 131 /** Updates the first run cling custom content hint */ 132 private void setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible, 133 boolean animate) { 134 final TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint); 135 if (ccHint != null) { 136 if (visible && !ccHintStr.isEmpty()) { 137 ccHint.setText(ccHintStr); 138 ccHint.setVisibility(View.VISIBLE); 139 if (animate) { 140 ccHint.setAlpha(0f); 141 ccHint.animate().alpha(1f) 142 .setDuration(SHOW_CLING_DURATION) 143 .start(); 144 } else { 145 ccHint.setAlpha(1f); 146 } 147 } else { 148 if (animate) { 149 ccHint.animate().alpha(0f) 150 .setDuration(SHOW_CLING_DURATION) 151 .setListener(new AnimatorListenerAdapter() { 152 @Override 153 public void onAnimationEnd(Animator animation) { 154 ccHint.setVisibility(View.GONE); 155 } 156 }) 157 .start(); 158 } else { 159 ccHint.setAlpha(0f); 160 ccHint.setVisibility(View.GONE); 161 } 162 } 163 } 164 } 165 166 /** Updates the first run cling custom content hint */ 167 public void updateCustomContentHintVisibility() { 168 Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling); 169 String ccHintStr = mLauncher.getFirstRunCustomContentHint(); 170 171 if (mLauncher.getWorkspace().hasCustomContent()) { 172 // Show the custom content hint if ccHintStr is not empty 173 if (cling != null) { 174 setCustomContentHintVisibility(cling, ccHintStr, true, true); 175 } 176 } else { 177 // Hide the custom content hint 178 if (cling != null) { 179 setCustomContentHintVisibility(cling, ccHintStr, false, true); 180 } 181 } 182 } 183 184 /** Updates the first run cling search bar hint. */ 185 public void updateSearchBarHint(String hint) { 186 Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling); 187 if (cling != null && cling.getVisibility() == View.VISIBLE && !hint.isEmpty()) { 188 TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint); 189 sbHint.setText(hint); 190 sbHint.setVisibility(View.VISIBLE); 191 } 192 } 193 194 public boolean shouldShowFirstRunOrMigrationClings() { 195 SharedPreferences sharedPrefs = mLauncher.getSharedPrefs(); 196 return areClingsEnabled() && 197 !sharedPrefs.getBoolean(FIRST_RUN_CLING_DISMISSED_KEY, false) && 198 !sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false); 199 } 200 201 public void removeFirstRunAndMigrationClings() { 202 removeCling(R.id.first_run_cling); 203 removeCling(R.id.migration_cling); 204 } 205 206 /** 207 * Shows the first run cling. 208 * 209 * This flow is mutually exclusive with showMigrationCling, and only runs if this Launcher 210 * package was preinstalled or there is no db to migrate from. 211 */ 212 public void showFirstRunCling() { 213 if (!skipCustomClingIfNoAccounts()) { 214 Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling); 215 if (cling != null) { 216 String sbHintStr = mLauncher.getFirstRunClingSearchBarHint(); 217 String ccHintStr = mLauncher.getFirstRunCustomContentHint(); 218 if (!sbHintStr.isEmpty()) { 219 TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint); 220 sbHint.setText(sbHintStr); 221 sbHint.setVisibility(View.VISIBLE); 222 } 223 setCustomContentHintVisibility(cling, ccHintStr, true, false); 224 } 225 initCling(R.id.first_run_cling, 0, false, true); 226 } else { 227 removeFirstRunAndMigrationClings(); 228 } 229 } 230 231 /** 232 * Shows the migration cling. 233 * 234 * This flow is mutually exclusive with showFirstRunCling, and only runs if this Launcher 235 * package was not preinstalled and there exists a db to migrate from. 236 */ 237 public void showMigrationCling() { 238 mLauncher.hideWorkspaceSearchAndHotseat(); 239 240 Cling c = initCling(R.id.migration_cling, 0, false, true); 241 c.bringScrimToFront(); 242 c.bringToFront(); 243 } 244 245 public void showMigrationWorkspaceCling() { 246 // Enable the clings only if they have not been dismissed before 247 if (areClingsEnabled() && !mLauncher.getSharedPrefs().getBoolean( 248 MIGRATION_WORKSPACE_CLING_DISMISSED_KEY, false)) { 249 Cling c = initCling(R.id.migration_workspace_cling, 0, false, true); 250 c.updateMigrationWorkspaceBubblePosition(); 251 c.bringScrimToFront(); 252 c.bringToFront(); 253 } else { 254 removeCling(R.id.migration_workspace_cling); 255 } 256 } 257 258 public void showWorkspaceCling() { 259 // Enable the clings only if they have not been dismissed before 260 if (areClingsEnabled() && !mLauncher.getSharedPrefs().getBoolean( 261 WORKSPACE_CLING_DISMISSED_KEY, false)) { 262 Cling c = initCling(R.id.workspace_cling, 0, false, true); 263 c.updateWorkspaceBubblePosition(); 264 265 // Set the focused hotseat app if there is one 266 c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(), 267 mLauncher.getFirstRunFocusedHotseatAppRank(), 268 mLauncher.getFirstRunFocusedHotseatAppComponentName(), 269 mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(), 270 mLauncher.getFirstRunFocusedHotseatAppBubbleDescription()); 271 } else { 272 removeCling(R.id.workspace_cling); 273 } 274 } 275 276 public Cling showFoldersCling() { 277 SharedPreferences sharedPrefs = mLauncher.getSharedPrefs(); 278 // Enable the clings only if they have not been dismissed before 279 if (areClingsEnabled() && 280 !sharedPrefs.getBoolean(FOLDER_CLING_DISMISSED_KEY, false) && 281 !sharedPrefs.getBoolean(Launcher.USER_HAS_MIGRATED, false)) { 282 Cling cling = initCling(R.id.folder_cling, R.id.cling_scrim, 283 true, true); 284 Folder openFolder = mLauncher.getWorkspace().getOpenFolder(); 285 if (openFolder != null) { 286 Rect openFolderRect = new Rect(); 287 openFolder.getHitRect(openFolderRect); 288 cling.setOpenFolderRect(openFolderRect); 289 openFolder.bringToFront(); 290 } 291 return cling; 292 } else { 293 removeCling(R.id.folder_cling); 294 return null; 295 } 296 } 297 298 public static void synchonouslyMarkFirstRunClingDismissed(Context ctx) { 299 SharedPreferences prefs = ctx.getSharedPreferences( 300 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); 301 SharedPreferences.Editor editor = prefs.edit(); 302 editor.putBoolean(LauncherClings.FIRST_RUN_CLING_DISMISSED_KEY, true); 303 editor.commit(); 304 } 305 306 public void markFolderClingDismissed() { 307 SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit(); 308 editor.putBoolean(LauncherClings.FOLDER_CLING_DISMISSED_KEY, true); 309 editor.apply(); 310 } 311 312 /** Removes the cling outright from the DragLayer */ 313 private void removeCling(int id) { 314 final View cling = mLauncher.findViewById(id); 315 if (cling != null) { 316 final ViewGroup parent = (ViewGroup) cling.getParent(); 317 parent.post(new Runnable() { 318 @Override 319 public void run() { 320 parent.removeView(cling); 321 } 322 }); 323 mHideFromAccessibilityHelper.restoreImportantForAccessibility(mLauncher.getDragLayer()); 324 } 325 } 326 327 /** Hides the specified Cling */ 328 private void dismissCling(final Cling cling, final Runnable postAnimationCb, 329 final String flag, int duration, boolean restoreNavBarVisibilty) { 330 // To catch cases where siblings of top-level views are made invisible, just check whether 331 // the cling is directly set to GONE before dismissing it. 332 if (cling != null && cling.getVisibility() != View.GONE) { 333 final Runnable cleanUpClingCb = new Runnable() { 334 public void run() { 335 cling.cleanup(); 336 SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit(); 337 editor.putBoolean(flag, true); 338 editor.apply(); 339 if (postAnimationCb != null) { 340 postAnimationCb.run(); 341 } 342 } 343 }; 344 if (duration <= 0) { 345 cleanUpClingCb.run(); 346 } else { 347 cling.hide(duration, cleanUpClingCb); 348 } 349 mHideFromAccessibilityHelper.restoreImportantForAccessibility(mLauncher.getDragLayer()); 350 351 if (restoreNavBarVisibilty) { 352 cling.setSystemUiVisibility(cling.getSystemUiVisibility() & 353 ~View.SYSTEM_UI_FLAG_LOW_PROFILE); 354 } 355 } 356 } 357 358 public void dismissFirstRunCling(View v) { 359 Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling); 360 Runnable cb = new Runnable() { 361 public void run() { 362 // Show the workspace cling next 363 showWorkspaceCling(); 364 } 365 }; 366 dismissCling(cling, cb, FIRST_RUN_CLING_DISMISSED_KEY, 367 DISMISS_CLING_DURATION, false); 368 369 // Fade out the search bar for the workspace cling coming up 370 mLauncher.getSearchBar().hideSearchBar(true); 371 } 372 373 private void dismissMigrationCling() { 374 mLauncher.showWorkspaceSearchAndHotseat(); 375 Runnable dismissCb = new Runnable() { 376 public void run() { 377 Cling cling = (Cling) mLauncher.findViewById(R.id.migration_cling); 378 Runnable cb = new Runnable() { 379 public void run() { 380 // Show the migration workspace cling next 381 showMigrationWorkspaceCling(); 382 } 383 }; 384 dismissCling(cling, cb, MIGRATION_CLING_DISMISSED_KEY, 385 DISMISS_CLING_DURATION, true); 386 } 387 }; 388 mLauncher.getWorkspace().post(dismissCb); 389 } 390 391 private void dismissAnyWorkspaceCling(Cling cling, String key, View v) { 392 Runnable cb = null; 393 if (v == null) { 394 cb = new Runnable() { 395 public void run() { 396 mLauncher.getWorkspace().enterOverviewMode(); 397 } 398 }; 399 } 400 dismissCling(cling, cb, key, DISMISS_CLING_DURATION, true); 401 402 // Fade in the search bar 403 mLauncher.getSearchBar().showSearchBar(true); 404 } 405 406 public void dismissMigrationClingCopyApps(View v) { 407 // Copy the shortcuts from the old database 408 LauncherModel model = mLauncher.getModel(); 409 model.resetLoadedState(false, true); 410 model.startLoader(false, PagedView.INVALID_RESTORE_PAGE, 411 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE 412 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); 413 414 // Set the flag to skip the folder cling 415 String spKey = LauncherAppState.getSharedPreferencesKey(); 416 SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE); 417 SharedPreferences.Editor editor = sp.edit(); 418 editor.putBoolean(Launcher.USER_HAS_MIGRATED, true); 419 editor.apply(); 420 421 // Disable the migration cling 422 dismissMigrationCling(); 423 } 424 425 public void dismissMigrationClingUseDefault(View v) { 426 // Clear the workspace 427 LauncherModel model = mLauncher.getModel(); 428 model.resetLoadedState(false, true); 429 model.startLoader(false, PagedView.INVALID_RESTORE_PAGE, 430 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE); 431 432 // Disable the migration cling 433 dismissMigrationCling(); 434 } 435 436 public void dismissMigrationWorkspaceCling(View v) { 437 Cling cling = (Cling) mLauncher.findViewById(R.id.migration_workspace_cling); 438 dismissAnyWorkspaceCling(cling, MIGRATION_WORKSPACE_CLING_DISMISSED_KEY, v); 439 } 440 441 public void dismissWorkspaceCling(View v) { 442 Cling cling = (Cling) mLauncher.findViewById(R.id.workspace_cling); 443 dismissAnyWorkspaceCling(cling, WORKSPACE_CLING_DISMISSED_KEY, v); 444 } 445 446 public void dismissFolderCling(View v) { 447 Cling cling = (Cling) mLauncher.findViewById(R.id.folder_cling); 448 dismissCling(cling, null, FOLDER_CLING_DISMISSED_KEY, 449 DISMISS_CLING_DURATION, true); 450 } 451 } 452