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.ObjectAnimator; 22 import android.animation.PropertyValuesHolder; 23 import android.app.ActivityManager; 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.graphics.drawable.Drawable; 27 import android.os.Bundle; 28 import android.os.UserManager; 29 import android.provider.Settings; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.view.View.OnLongClickListener; 34 import android.view.ViewGroup; 35 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 36 import android.view.accessibility.AccessibilityManager; 37 38 class LauncherClings implements OnClickListener { 39 private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed"; 40 private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed"; 41 42 private static final String TAG_CROP_TOP_AND_SIDES = "crop_bg_top_and_sides"; 43 44 private static final boolean DISABLE_CLINGS = false; 45 46 private static final int SHOW_CLING_DURATION = 250; 47 private static final int DISMISS_CLING_DURATION = 200; 48 49 // New Secure Setting in L 50 private static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints"; 51 52 private Launcher mLauncher; 53 private LayoutInflater mInflater; 54 55 /** Ctor */ LauncherClings(Launcher launcher)56 public LauncherClings(Launcher launcher) { 57 mLauncher = launcher; 58 mInflater = LayoutInflater.from(mLauncher); 59 } 60 61 @Override onClick(View v)62 public void onClick(View v) { 63 int id = v.getId(); 64 if (id == R.id.cling_dismiss_migration_use_default) { 65 // Disable the migration cling 66 dismissMigrationCling(); 67 } else if (id == R.id.cling_dismiss_migration_copy_apps) { 68 // Copy the shortcuts from the old database 69 LauncherModel model = mLauncher.getModel(); 70 model.resetLoadedState(false, true); 71 model.startLoader(false, PagedView.INVALID_RESTORE_PAGE, 72 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE 73 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); 74 // Set the flag to skip the folder cling 75 String spKey = LauncherAppState.getSharedPreferencesKey(); 76 SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE); 77 SharedPreferences.Editor editor = sp.edit(); 78 editor.putBoolean(Launcher.USER_HAS_MIGRATED, true); 79 editor.apply(); 80 // Disable the migration cling 81 dismissMigrationCling(); 82 } else if (id == R.id.cling_dismiss_longpress_info) { 83 dismissLongPressCling(); 84 } 85 } 86 87 /** 88 * Shows the migration cling. 89 * 90 * This flow is mutually exclusive with showFirstRunCling, and only runs if this Launcher 91 * package was not preinstalled and there exists a db to migrate from. 92 */ showMigrationCling()93 public void showMigrationCling() { 94 mLauncher.hideWorkspaceSearchAndHotseat(); 95 96 ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher); 97 View inflated = mInflater.inflate(R.layout.migration_cling, root); 98 inflated.findViewById(R.id.cling_dismiss_migration_copy_apps).setOnClickListener(this); 99 inflated.findViewById(R.id.cling_dismiss_migration_use_default).setOnClickListener(this); 100 } 101 dismissMigrationCling()102 private void dismissMigrationCling() { 103 mLauncher.showWorkspaceSearchAndHotseat(); 104 Runnable dismissCb = new Runnable() { 105 public void run() { 106 Runnable cb = new Runnable() { 107 public void run() { 108 // Show the longpress cling next 109 showLongPressCling(false); 110 } 111 }; 112 dismissCling(mLauncher.findViewById(R.id.migration_cling), cb, 113 MIGRATION_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); 114 } 115 }; 116 mLauncher.getWorkspace().post(dismissCb); 117 } 118 showLongPressCling(boolean showWelcome)119 public void showLongPressCling(boolean showWelcome) { 120 ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher); 121 View cling = mInflater.inflate(R.layout.longpress_cling, root, false); 122 123 cling.setOnLongClickListener(new OnLongClickListener() { 124 125 @Override 126 public boolean onLongClick(View v) { 127 mLauncher.getWorkspace().enterOverviewMode(); 128 dismissLongPressCling(); 129 return true; 130 } 131 }); 132 133 final ViewGroup content = (ViewGroup) cling.findViewById(R.id.cling_content); 134 mInflater.inflate(showWelcome ? R.layout.longpress_cling_welcome_content 135 : R.layout.longpress_cling_content, content); 136 content.findViewById(R.id.cling_dismiss_longpress_info).setOnClickListener(this); 137 138 if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { 139 Drawable bg = new BorderCropDrawable(mLauncher.getResources().getDrawable(R.drawable.cling_bg), 140 true, true, true, false); 141 content.setBackground(bg); 142 } 143 144 root.addView(cling); 145 146 if (showWelcome) { 147 // This is the first cling being shown. No need to animate. 148 return; 149 } 150 151 // Animate 152 content.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 153 154 @Override 155 public void onGlobalLayout() { 156 content.getViewTreeObserver().removeOnGlobalLayoutListener(this); 157 158 ObjectAnimator anim; 159 if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { 160 content.setTranslationY(-content.getMeasuredHeight()); 161 anim = LauncherAnimUtils.ofFloat(content, "translationY", 0); 162 } else { 163 content.setScaleX(0); 164 content.setScaleY(0); 165 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1); 166 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1); 167 anim = LauncherAnimUtils.ofPropertyValuesHolder(content, scaleX, scaleY); 168 } 169 170 anim.setDuration(SHOW_CLING_DURATION); 171 anim.setInterpolator(new LogDecelerateInterpolator(100, 0)); 172 anim.start(); 173 } 174 }); 175 } 176 dismissLongPressCling()177 private void dismissLongPressCling() { 178 Runnable dismissCb = new Runnable() { 179 public void run() { 180 dismissCling(mLauncher.findViewById(R.id.longpress_cling), null, 181 WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); 182 } 183 }; 184 mLauncher.getWorkspace().post(dismissCb); 185 } 186 187 /** Hides the specified Cling */ dismissCling(final View cling, final Runnable postAnimationCb, final String flag, int duration)188 private void dismissCling(final View cling, final Runnable postAnimationCb, 189 final String flag, int duration) { 190 // To catch cases where siblings of top-level views are made invisible, just check whether 191 // the cling is directly set to GONE before dismissing it. 192 if (cling != null && cling.getVisibility() != View.GONE) { 193 final Runnable cleanUpClingCb = new Runnable() { 194 public void run() { 195 cling.setVisibility(View.GONE); 196 mLauncher.getSharedPrefs().edit() 197 .putBoolean(flag, true) 198 .apply(); 199 if (postAnimationCb != null) { 200 postAnimationCb.run(); 201 } 202 } 203 }; 204 if (duration <= 0) { 205 cleanUpClingCb.run(); 206 } else { 207 cling.animate().alpha(0).setDuration(duration).withEndAction(cleanUpClingCb); 208 } 209 } 210 } 211 212 /** Returns whether the clings are enabled or should be shown */ areClingsEnabled()213 private boolean areClingsEnabled() { 214 if (DISABLE_CLINGS) { 215 return false; 216 } 217 218 // disable clings when running in a test harness 219 if(ActivityManager.isRunningInTestHarness()) return false; 220 221 // Disable clings for accessibility when explore by touch is enabled 222 final AccessibilityManager a11yManager = (AccessibilityManager) mLauncher.getSystemService( 223 Launcher.ACCESSIBILITY_SERVICE); 224 if (a11yManager.isTouchExplorationEnabled()) { 225 return false; 226 } 227 228 // Restricted secondary users (child mode) will potentially have very few apps 229 // seeded when they start up for the first time. Clings won't work well with that 230 boolean supportsLimitedUsers = 231 android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 232 Account[] accounts = AccountManager.get(mLauncher).getAccounts(); 233 if (supportsLimitedUsers && accounts.length == 0) { 234 UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE); 235 Bundle restrictions = um.getUserRestrictions(); 236 if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { 237 return false; 238 } 239 } 240 if (Settings.Secure.getInt(mLauncher.getContentResolver(), SKIP_FIRST_USE_HINTS, 0) 241 == 1) { 242 return false; 243 } 244 return true; 245 } 246 shouldShowFirstRunOrMigrationClings()247 public boolean shouldShowFirstRunOrMigrationClings() { 248 SharedPreferences sharedPrefs = mLauncher.getSharedPrefs(); 249 return areClingsEnabled() && 250 !sharedPrefs.getBoolean(WORKSPACE_CLING_DISMISSED_KEY, false) && 251 !sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false); 252 } 253 synchonouslyMarkFirstRunClingDismissed(Context ctx)254 public static void synchonouslyMarkFirstRunClingDismissed(Context ctx) { 255 SharedPreferences prefs = ctx.getSharedPreferences( 256 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); 257 SharedPreferences.Editor editor = prefs.edit(); 258 editor.putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true); 259 editor.commit(); 260 } 261 } 262