LauncherClings.java revision c6c0367111e587c7f19cb8185dcda550aa94a848
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
17package com.android.launcher3;
18
19import android.accounts.Account;
20import android.accounts.AccountManager;
21import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.app.ActivityManager;
24import android.content.Context;
25import android.content.SharedPreferences;
26import android.graphics.Rect;
27import android.os.Bundle;
28import android.os.UserManager;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.accessibility.AccessibilityManager;
33import android.widget.TextView;
34
35class 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