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