• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
LauncherClings(Launcher launcher)54     public LauncherClings(Launcher launcher) {
55         mLauncher = launcher;
56         mInflater = mLauncher.getLayoutInflater();
57     }
58 
59     /** Initializes a cling */
initCling(int clingId, int scrimId, boolean animate, boolean dimNavBarVisibilty)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 */
areClingsEnabled()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. */
isFolderClingVisible()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 
skipCustomClingIfNoAccounts()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 */
setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible, boolean animate)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 */
updateCustomContentHintVisibility()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. */
updateSearchBarHint(String 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 
shouldShowFirstRunOrMigrationClings()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 
removeFirstRunAndMigrationClings()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      */
showFirstRunCling()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      */
showMigrationCling()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 
showMigrationWorkspaceCling()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 
showWorkspaceCling()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 
showFoldersCling()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 
synchonouslyMarkFirstRunClingDismissed(Context ctx)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 
markFolderClingDismissed()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 */
removeCling(int id)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 */
dismissCling(final Cling cling, final Runnable postAnimationCb, final String flag, int duration, boolean restoreNavBarVisibilty)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 
dismissFirstRunCling(View v)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 
dismissMigrationCling()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 
dismissAnyWorkspaceCling(Cling cling, String key, View v)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 
dismissMigrationClingCopyApps(View v)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 
dismissMigrationClingUseDefault(View v)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 
dismissMigrationWorkspaceCling(View v)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 
dismissWorkspaceCling(View v)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 
dismissFolderCling(View v)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