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