Home | History | Annotate | Download | only in launcher3
      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