diff options
Diffstat (limited to 'src/com/android/launcher3/Launcher.java')
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 1804 |
1 files changed, 1242 insertions, 562 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index c22a6bf3f..42ec4fb48 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -22,11 +22,13 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.AlertDialog; import android.app.SearchManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; @@ -37,6 +39,7 @@ import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -44,25 +47,25 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; -import android.provider.Settings; import android.speech.RecognizerIntent; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -70,6 +73,7 @@ import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.DisplayMetrics; import android.util.Log; +import android.view.ContextThemeWrapper; import android.view.Display; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -81,13 +85,16 @@ import android.view.Surface; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; +import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; import android.widget.Advanceable; import android.widget.FrameLayout; @@ -96,6 +103,14 @@ import android.widget.TextView; import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.PagedView.PageSwitchListener; +import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.compat.LauncherActivityInfoCompat; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -105,6 +120,9 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; @@ -113,13 +131,12 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; - /** * Default launcher application. */ public class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, - View.OnTouchListener { + View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener { static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -133,12 +150,12 @@ public class Launcher extends Activity private static final int REQUEST_CREATE_SHORTCUT = 1; private static final int REQUEST_CREATE_APPWIDGET = 5; - private static final int REQUEST_PICK_APPLICATION = 6; private static final int REQUEST_PICK_SHORTCUT = 7; private static final int REQUEST_PICK_APPWIDGET = 9; private static final int REQUEST_PICK_WALLPAPER = 10; private static final int REQUEST_BIND_APPWIDGET = 11; + private static final int REQUEST_RECONFIGURE_APPWIDGET = 12; /** * IntentStarter uses request codes starting with this. This must be greater than all activity @@ -189,9 +206,13 @@ public class Launcher extends Activity // Type: int[] private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids"; - + static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed"; static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed"; + static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete"; + static final String ACTION_FIRST_LOAD_COMPLETE = + "com.android.launcher3.action.FIRST_LOAD_COMPLETE"; + private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon"; private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME = "com.android.launcher.toolbar_search_icon"; @@ -208,10 +229,12 @@ public class Launcher extends Activity private State mState = State.WORKSPACE; private AnimatorSet mStateAnimation; + private boolean mIsSafeModeEnabled; + static final int APPWIDGET_HOST_ID = 1024; public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300; - public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE = 400; private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; + private static final int ACTIVITY_START_DELAY = 1000; private static final Object sLock = new Object(); private static int sScreen = DEFAULT_SCREEN; @@ -223,6 +246,7 @@ public class Launcher extends Activity private static int NEW_APPS_PAGE_MOVE_DELAY = 500; private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; private static int NEW_APPS_ANIMATION_DELAY = 500; + private static final int SINGLE_FRAME_DELAY = 16; private final BroadcastReceiver mCloseSystemDialogsReceiver = new CloseSystemDialogsIntentReceiver(); @@ -236,9 +260,8 @@ public class Launcher extends Activity private DragLayer mDragLayer; private DragController mDragController; private View mWeightWatcher; - private LauncherClings mLauncherClings; - private AppWidgetManager mAppWidgetManager; + private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; private ItemInfo mPendingAddInfo = new ItemInfo(); @@ -278,9 +301,6 @@ public class Launcher extends Activity private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>(); private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>(); - // Keep track of whether the user has left launcher - private static boolean sPausedFromUserAction = false; - private Bundle mSavedInstanceState; private LauncherModel mModel; @@ -312,10 +332,6 @@ public class Launcher extends Activity // External icons saved in case of resource changes, orientation, etc. private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2]; private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2]; - private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2]; - - private Intent mAppMarketIntent = null; - private static final boolean DISABLE_MARKET_BUTTON = true; private Drawable mWorkspaceBackgroundDrawable; @@ -352,8 +368,7 @@ public class Launcher extends Activity } }; - private static ArrayList<PendingAddArguments> sPendingAddList - = new ArrayList<PendingAddArguments>(); + private static PendingAddArguments sPendingAddItem; public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY); @@ -364,10 +379,13 @@ public class Launcher extends Activity long screenId; int cellX; int cellY; + int appWidgetId; } private Stats mStats; + FocusIndicatorView mFocusHandler; + static boolean isPropertyEnabled(String propertyName) { return Log.isLoggable(propertyName, Log.VERBOSE); } @@ -393,7 +411,7 @@ public class Launcher extends Activity LauncherAppState.setApplicationContext(getApplicationContext()); LauncherAppState app = LauncherAppState.getInstance(); - + LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this); // Determine the dynamic grid properties Point smallestSize = new Point(); Point largestSize = new Point(); @@ -414,16 +432,16 @@ public class Launcher extends Activity // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); + mIsSafeModeEnabled = getPackageManager().isSafeMode(); mModel = app.setLauncher(this); mIconCache = app.getIconCache(); mIconCache.flushInvalidIcons(grid); mDragController = new DragController(this); - mLauncherClings = new LauncherClings(this); mInflater = getLayoutInflater(); mStats = new Stats(this); - mAppWidgetManager = AppWidgetManager.getInstance(this); + mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); mAppWidgetHost.startListening(); @@ -438,7 +456,6 @@ public class Launcher extends Activity Environment.getExternalStorageDirectory() + "/launcher"); } - checkForLocaleChange(); setContentView(R.layout.launcher); @@ -457,7 +474,7 @@ public class Launcher extends Activity } if (!mRestoring) { - if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE || sPausedFromUserAction) { + if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { // If the user leaves launcher, then we should just load items asynchronously when // they return. mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); @@ -480,25 +497,16 @@ public class Launcher extends Activity // On large interfaces, we want the screen to auto-rotate based on the current orientation unlockScreenOrientation(true); - // The two first run cling paths are mutually exclusive, if the launcher is preinstalled - // on the device, then we always show the first run cling experience (or if there is no - // launcher2). Otherwise, we prompt the user upon started for migration - showFirstRunActivity(); - if (mLauncherClings.shouldShowFirstRunOrMigrationClings()) { - if (mModel.canMigrateFromOldLauncherDb(this)) { - mLauncherClings.showMigrationCling(); - } else { - mLauncherClings.showFirstRunCling(); - } + if (shouldShowIntroScreen()) { + showIntroScreen(); } else { - mLauncherClings.removeFirstRunAndMigrationClings(); + showFirstRunActivity(); + showFirstRunClings(); } } - protected void onUserLeaveHint() { - super.onUserLeaveHint(); - sPausedFromUserAction = true; - } + @Override + public void onLauncherProviderChange() { } /** To be overriden by subclasses to hint to Launcher that we have custom content */ protected boolean hasCustomContentToLeft() { @@ -514,21 +522,6 @@ public class Launcher extends Activity } /** - * To be overridden by subclasses to indicate that there is an activity to launch - * before showing the standard launcher experience. - */ - protected boolean hasFirstRunActivity() { - return false; - } - - /** - * To be overridden by subclasses to launch any first run activity - */ - protected Intent getFirstRunActivity() { - return null; - } - - /** * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to * ensure the custom content page is added or removed if necessary. */ @@ -552,11 +545,7 @@ public class Launcher extends Activity boolean voiceVisible = false; // If we have a saved version of these external icons, we load them up immediately int coi = getCurrentOrientationIndexForGlobalIcons(); - if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null || - sAppMarketIcon[coi] == null) { - if (!DISABLE_MARKET_BUTTON) { - updateAppMarketIcon(); - } + if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null) { searchVisible = updateGlobalSearchIcon(); voiceVisible = updateVoiceSearchIcon(searchVisible); } @@ -568,9 +557,6 @@ public class Launcher extends Activity updateVoiceSearchIcon(sVoiceSearchIcon[coi]); voiceVisible = true; } - if (!DISABLE_MARKET_BUTTON && sAppMarketIcon[coi] != null) { - updateAppMarketIcon(sAppMarketIcon[coi]); - } if (mSearchDropTargetBar != null) { mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible); } @@ -703,19 +689,20 @@ public class Launcher extends Activity } } - /** - * Copied from View -- the View version of the method isn't called - * anywhere else in our process and only exists for API level 17+, - * so it's ok to keep our own version with no API requirement. - */ public static int generateViewId() { - for (;;) { - final int result = sNextGeneratedId.get(); - // aapt-generated IDs have the high byte nonzero; clamp to the range under that. - int newValue = result + 1; - if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. - if (sNextGeneratedId.compareAndSet(result, newValue)) { - return result; + if (Build.VERSION.SDK_INT >= 17) { + return View.generateViewId(); + } else { + // View.generateViewId() is not available. The following fallback logic is a copy + // of its implementation. + for (;;) { + final int result = sNextGeneratedId.get(); + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + int newValue = result + 1; + if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. + if (sNextGeneratedId.compareAndSet(result, newValue)) { + return result; + } } } } @@ -735,40 +722,39 @@ public class Launcher extends Activity * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have * a configuration step, this allows the proper animations to run after other transitions. */ - private boolean completeAdd(PendingAddArguments args) { - boolean result = false; + private long completeAdd(PendingAddArguments args) { + long screenId = args.screenId; + if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + // When the screen id represents an actual screen (as opposed to a rank) we make sure + // that the drop page actually exists. + screenId = ensurePendingDropLayoutExists(args.screenId); + } + switch (args.requestCode) { - case REQUEST_PICK_APPLICATION: - completeAddApplication(args.intent, args.container, args.screenId, args.cellX, - args.cellY); - break; - case REQUEST_PICK_SHORTCUT: - processShortcut(args.intent); - break; case REQUEST_CREATE_SHORTCUT: - completeAddShortcut(args.intent, args.container, args.screenId, args.cellX, + completeAddShortcut(args.intent, args.container, screenId, args.cellX, args.cellY); - result = true; break; case REQUEST_CREATE_APPWIDGET: - int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); - completeAddAppWidget(appWidgetId, args.container, args.screenId, null, null); - result = true; + completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null); + break; + case REQUEST_RECONFIGURE_APPWIDGET: + completeRestoreAppWidget(args.appWidgetId); break; } // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, // if you turned the screen off and then back while in All Apps, Launcher would not // return to the workspace. Clearing mAddInfo.container here fixes this issue resetAddInfo(); - return result; + return screenId; } @Override protected void onActivityResult( final int requestCode, final int resultCode, final Intent data) { // Reset the startActivity waiting flag - mWaitingForResult = false; - int pendingAddWidgetId = mPendingAddWidgetId; + setWaitingForResult(false); + final int pendingAddWidgetId = mPendingAddWidgetId; mPendingAddWidgetId = -1; Runnable exitSpringLoaded = new Runnable() { @@ -784,7 +770,7 @@ public class Launcher extends Activity data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1; if (resultCode == RESULT_CANCELED) { completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId); - mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded, + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } else if (resultCode == RESULT_OK) { addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, @@ -801,6 +787,7 @@ public class Launcher extends Activity boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET); + final boolean workspaceLocked = isWorkspaceLocked(); // We have special handling for widgets if (isWidgetDrop) { final int appWidgetId; @@ -813,33 +800,66 @@ public class Launcher extends Activity } final int result; - final Runnable onComplete; if (appWidgetId < 0 || resultCode == RESULT_CANCELED) { - Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" + - "widget configuration activity."); + Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " + + "returned from the widget configuration activity."); result = RESULT_CANCELED; completeTwoStageWidgetDrop(result, appWidgetId); - onComplete = new Runnable() { + final Runnable onComplete = new Runnable() { @Override public void run() { exitSpringLoadedDragModeDelayed(false, 0, null); } }; + if (workspaceLocked) { + // No need to remove the empty screen if we're mid-binding, as the + // the bind will not add the empty screen. + mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY); + } else { + mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } } else { - result = resultCode; - final CellLayout dropLayout = - (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId); - dropLayout.setDropPending(true); - onComplete = new Runnable() { - @Override - public void run() { - completeTwoStageWidgetDrop(result, appWidgetId); - dropLayout.setDropPending(false); + if (!workspaceLocked) { + if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + // When the screen id represents an actual screen (as opposed to a rank) + // we make sure that the drop page actually exists. + mPendingAddInfo.screenId = + ensurePendingDropLayoutExists(mPendingAddInfo.screenId); } - }; + final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId); + + dropLayout.setDropPending(true); + final Runnable onComplete = new Runnable() { + @Override + public void run() { + completeTwoStageWidgetDrop(resultCode, appWidgetId); + dropLayout.setDropPending(false); + } + }; + mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } else { + PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId, + mPendingAddInfo); + sPendingAddItem = args; + } + } + return; + } + + if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) { + if (resultCode == RESULT_OK) { + // Update the widget view. + PendingAddArguments args = preparePendingAddArgs(requestCode, data, + pendingAddWidgetId, mPendingAddInfo); + if (workspaceLocked) { + sPendingAddItem = args; + } else { + completeAdd(args); + } } - mWorkspace.removeExtraEmptyScreen(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY, - false); + // Leave the widget in the pending state if the user canceled the configure. return; } @@ -849,27 +869,54 @@ public class Launcher extends Activity // For example, the user would PICK_SHORTCUT for "Music playlist", and we // launch over to the Music app to actually CREATE_SHORTCUT. if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { - final PendingAddArguments args = new PendingAddArguments(); - args.requestCode = requestCode; - args.intent = data; - args.container = mPendingAddInfo.container; - args.screenId = mPendingAddInfo.screenId; - args.cellX = mPendingAddInfo.cellX; - args.cellY = mPendingAddInfo.cellY; + final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, + mPendingAddInfo); if (isWorkspaceLocked()) { - sPendingAddList.add(args); + sPendingAddItem = args; } else { completeAdd(args); + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } - mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } else if (resultCode == RESULT_CANCELED) { - mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded, + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } mDragLayer.clearAnimatedView(); } + private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int + appWidgetId, ItemInfo info) { + PendingAddArguments args = new PendingAddArguments(); + args.requestCode = requestCode; + args.intent = data; + args.container = info.container; + args.screenId = info.screenId; + args.cellX = info.cellX; + args.cellY = info.cellY; + args.appWidgetId = appWidgetId; + return args; + } + + /** + * Check to see if a given screen id exists. If not, create it at the end, return the new id. + * + * @param screenId the screen id to check + * @return the new screen, or screenId if it exists + */ + private long ensurePendingDropLayoutExists(long screenId) { + CellLayout dropLayout = + (CellLayout) mWorkspace.getScreenWithId(screenId); + if (dropLayout == null) { + // it's possible that the add screen was removed because it was + // empty and a re-bind occurred + mWorkspace.addExtraEmptyScreen(); + return mWorkspace.commitExtraEmptyScreen(); + } else { + return screenId; + } + } + private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) { CellLayout cellLayout = (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId); @@ -938,9 +985,8 @@ public class Launcher extends Activity setWorkspaceBackground(mState == State.WORKSPACE); mPaused = false; - sPausedFromUserAction = false; if (mRestoring || mOnResumeNeedsLoad) { - mWorkspaceLoading = true; + setWorkspaceLoading(true); mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE); mRestoring = false; mOnResumeNeedsLoad = false; @@ -981,10 +1027,6 @@ public class Launcher extends Activity // Resets the previous workspace icon press state mWaitingForResume.setStayPressed(false); } - if (mAppsCustomizeContent != null) { - // Resets the previous all apps icon press state - mAppsCustomizeContent.resetDrawableState(); - } // It is possible that widgets can receive updates while launcher is not in the foreground. // Consequently, the widgets will be inflated in the orientation of the foreground activity @@ -1010,17 +1052,20 @@ public class Launcher extends Activity // It is also poassible that onShow will instead be called slightly after first layout // if PagedView#setRestorePage was set to the custom content page in onCreate(). if (mWorkspace.isOnOrMovingToCustomContent()) { - mWorkspace.getCustomContentCallbacks().onShow(); + mWorkspace.getCustomContentCallbacks().onShow(true); } } mWorkspace.updateInteractionForState(); mWorkspace.onResume(); + + PackageInstallerCompat.getInstance(this).onResume(); } @Override protected void onPause() { // Ensure that items added to Launcher are queued until Launcher returns InstallShortcutReceiver.enableInstallQueue(); + PackageInstallerCompat.getInstance(this).onPause(); super.onPause(); mPaused = true; @@ -1054,23 +1099,24 @@ public class Launcher extends Activity } public interface CustomContentCallbacks { - // Custom content is completely shown - public void onShow(); + // Custom content is completely shown. {@code fromResume} indicates whether this was caused + // by a onResume or by scrolling otherwise. + public void onShow(boolean fromResume); // Custom content is completely hidden public void onHide(); // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing). public void onScrollProgressChanged(float progress); + + // Indicates whether the user is allowed to scroll away from the custom content. + boolean isScrollingAllowed(); } protected boolean hasSettings() { return false; } - protected void startSettings() { - } - public interface QSBScroller { public void setScrollY(int scrollY); } @@ -1089,7 +1135,9 @@ public class Launcher extends Activity @Override public Object onRetainNonConfigurationInstance() { // Flag the loader to stop early before switching - mModel.stopLoader(); + if (mModel.isCurrentCallbacks(this)) { + mModel.stopLoader(); + } if (mAppsCustomizeContent != null) { mAppsCustomizeContent.surrender(); } @@ -1196,7 +1244,7 @@ public class Launcher extends Activity mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID); - mWaitingForResult = true; + setWaitingForResult(true); mRestoring = true; } @@ -1231,8 +1279,10 @@ public class Launcher extends Activity final DragController dragController = mDragController; mLauncherView = findViewById(R.id.launcher); + mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator); mDragLayer = (DragLayer) findViewById(R.id.drag_layer); mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); + mWorkspace.setPageSwitchListener(this); mPageIndicators = mDragLayer.findViewById(R.id.page_indicator); mLauncherView.setSystemUiVisibility( @@ -1255,7 +1305,7 @@ public class Launcher extends Activity @Override public void onClick(View arg0) { if (!mWorkspace.isSwitchingState()) { - showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true); + onClickAddWidgetButton(arg0); } } }); @@ -1266,7 +1316,7 @@ public class Launcher extends Activity @Override public void onClick(View arg0) { if (!mWorkspace.isSwitchingState()) { - startWallpaper(); + onClickWallpaperPicker(arg0); } } }); @@ -1278,9 +1328,9 @@ public class Launcher extends Activity @Override public void onClick(View arg0) { if (!mWorkspace.isSwitchingState()) { - startSettings(); - } + onClickSettingsButton(arg0); } + } }); settingsButton.setOnTouchListener(getHapticFeedbackTouchListener()); } else { @@ -1334,6 +1384,17 @@ public class Launcher extends Activity } /** + * Sets the all apps button. This method is called from {@link Hotseat}. + */ + public void setAllAppsButton(View allAppsButton) { + mAllAppsButton = allAppsButton; + } + + public View getAllAppsButton() { + return mAllAppsButton; + } + + /** * Creates a view representing a shortcut. * * @param info The data structure describing the shortcut. @@ -1356,44 +1417,13 @@ public class Launcher extends Activity */ View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) { BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false); - favorite.applyFromShortcutInfo(info, mIconCache); + favorite.applyFromShortcutInfo(info, mIconCache, true); favorite.setOnClickListener(this); + favorite.setOnFocusChangeListener(mFocusHandler); return favorite; } /** - * Add an application shortcut to the workspace. - * - * @param data The intent describing the application. - * @param cellInfo The position on screen where to create the shortcut. - */ - void completeAddApplication(Intent data, long container, long screenId, int cellX, int cellY) { - final int[] cellXY = mTmpAddItemCellCoordinates; - final CellLayout layout = getCellLayout(container, screenId); - - // First we check if we already know the exact location where we want to add this item. - if (cellX >= 0 && cellY >= 0) { - cellXY[0] = cellX; - cellXY[1] = cellY; - } else if (!layout.findCellForSpan(cellXY, 1, 1)) { - showOutOfSpaceMessage(isHotseatLayout(layout)); - return; - } - - final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this); - - if (info != null) { - info.setActivity(this, data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - info.container = ItemInfo.NO_ID; - mWorkspace.addApplicationShortcut(info, layout, container, screenId, cellXY[0], cellXY[1], - isWorkspaceLocked(), cellX, cellY); - } else { - Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data); - } - } - - /** * Add a shortcut to the workspace. * * @param data The intent describing the shortcut. @@ -1543,6 +1573,7 @@ public class Launcher extends Activity launcherInfo.spanY = spanXY[1]; launcherInfo.minSpanX = mPendingAddInfo.minSpanX; launcherInfo.minSpanY = mPendingAddInfo.minSpanY; + launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo); LauncherModel.addItemToDatabase(this, launcherInfo, container, screenId, cellXY[0], cellXY[1], false); @@ -1595,6 +1626,9 @@ public class Launcher extends Activity mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); + } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) + || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { + getModel().forceReload(); } } }; @@ -1607,16 +1641,61 @@ public class Launcher extends Activity final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); + // For handling managed profiles + filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED); if (ENABLE_DEBUG_INTENTS) { filter.addAction(DebugIntents.DELETE_DATABASE); filter.addAction(DebugIntents.MIGRATE_DATABASE); } registerReceiver(mReceiver, filter); FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); + setupTransparentSystemBarsForLmp(); mAttached = true; mVisible = true; } + /** + * Sets up transparent navigation and status bars in LMP. + * This method is a no-op for other platform versions. + */ + @TargetApi(19) + private void setupTransparentSystemBarsForLmp() { + // TODO(sansid): use the APIs directly when compiling against L sdk. + // Currently we use reflection to access the flags and the API to set the transparency + // on the System bars. + if (Utilities.isLmpOrAbove()) { + try { + getWindow().getAttributes().systemUiVisibility |= + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField( + "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS"); + getWindow().addFlags(drawsSysBackgroundsField.getInt(null)); + + Method setStatusBarColorMethod = + Window.class.getDeclaredMethod("setStatusBarColor", int.class); + Method setNavigationBarColorMethod = + Window.class.getDeclaredMethod("setNavigationBarColor", int.class); + setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT); + setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT); + } catch (NoSuchFieldException e) { + Log.w(TAG, "NoSuchFieldException while setting up transparent bars"); + } catch (NoSuchMethodException ex) { + Log.w(TAG, "NoSuchMethodException while setting up transparent bars"); + } catch (IllegalAccessException e) { + Log.w(TAG, "IllegalAccessException while setting up transparent bars"); + } catch (IllegalArgumentException e) { + Log.w(TAG, "IllegalArgumentException while setting up transparent bars"); + } catch (InvocationTargetException e) { + Log.w(TAG, "InvocationTargetException while setting up transparent bars"); + } finally {} + } + } + @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); @@ -1667,11 +1746,6 @@ public class Launcher extends Activity } }); } - // When Launcher comes back to foreground, a different Activity might be responsible for - // the app market intent, so refresh the icon - if (!DISABLE_MARKET_BUTTON) { - updateAppMarketIcon(); - } clearTypedText(); } } @@ -1778,10 +1852,6 @@ public class Launcher extends Activity return mModel; } - public LauncherClings getLauncherClings() { - return mLauncherClings; - } - protected SharedPreferences getSharedPrefs() { return mSharedPrefs; } @@ -1790,7 +1860,7 @@ public class Launcher extends Activity getWindow().closeAllPanels(); // Whatever we were doing is hereby canceled. - mWaitingForResult = false; + setWaitingForResult(false); } @Override @@ -1930,8 +2000,13 @@ public class Launcher extends Activity // Stop callbacks from LauncherModel LauncherAppState app = (LauncherAppState.getInstance()); - mModel.stopLoader(); - app.setLauncher(null); + + // It's possible to receive onDestroy after a new Launcher activity has + // been created. In this case, don't interfere with the new Launcher. + if (mModel.isCurrentCallbacks(this)) { + mModel.stopLoader(); + app.setLauncher(null); + } try { mAppWidgetHost.stopListening(); @@ -1959,6 +2034,7 @@ public class Launcher extends Activity mWorkspace = null; mDragController = null; + PackageInstallerCompat.getInstance(this).onStop(); LauncherAnimUtils.onDestroyActivity(); } @@ -1968,7 +2044,9 @@ public class Launcher extends Activity @Override public void startActivityForResult(Intent intent, int requestCode) { - if (requestCode >= 0) mWaitingForResult = true; + if (requestCode >= 0) { + setWaitingForResult(true); + } super.startActivityForResult(intent, requestCode); } @@ -1995,14 +2073,25 @@ public class Launcher extends Activity sourceBounds = mSearchDropTargetBar.getSearchBarBounds(); } - startSearch(initialQuery, selectInitialQuery, + boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds); + if (clearTextImmediately) { + clearTypedText(); + } } - public void startSearch(String initialQuery, + /** + * Start a text search. + * + * @return {@code true} if the search will start immediately, so any further keypresses + * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue + * to buffer keypresses. + */ + public boolean startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) { startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds); + return false; } /** @@ -2082,6 +2171,24 @@ public class Launcher extends Activity return mWorkspaceLoading; } + private void setWorkspaceLoading(boolean value) { + boolean isLocked = isWorkspaceLocked(); + mWorkspaceLoading = value; + if (isLocked != isWorkspaceLocked()) { + onWorkspaceLockedChanged(); + } + } + + private void setWaitingForResult(boolean value) { + boolean isLocked = isWorkspaceLocked(); + mWaitingForResult = value; + if (isLocked != isWorkspaceLocked()) { + onWorkspaceLockedChanged(); + } + } + + protected void onWorkspaceLockedChanged() { } + private void resetAddInfo() { mPendingAddInfo.container = ItemInfo.NO_ID; mPendingAddInfo.screenId = -1; @@ -2104,10 +2211,9 @@ public class Launcher extends Activity mPendingAddWidgetId = appWidgetId; // Launch over to configure widget, if needed - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); - intent.setComponent(appWidgetInfo.configure); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_APPWIDGET); + mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this, + mAppWidgetHost, REQUEST_CREATE_APPWIDGET); + } else { // Otherwise just add it Runnable onComplete = new Runnable() { @@ -2120,7 +2226,7 @@ public class Launcher extends Activity }; completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget, appWidgetInfo); - mWorkspace.removeExtraEmptyScreen(true, onComplete, delay, false); + mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); } } @@ -2191,14 +2297,8 @@ public class Launcher extends Activity appWidgetId = getAppWidgetHost().allocateAppWidgetId(); Bundle options = info.bindOptions; - boolean success = false; - if (options != null) { - success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, - info.componentName, options); - } else { - success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, - info.componentName); - } + boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( + appWidgetId, info.info, options); if (success) { addAppWidgetImpl(appWidgetId, info, null, info.info); } else { @@ -2206,6 +2306,8 @@ public class Launcher extends Activity Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName); + mAppWidgetManager.getUser(mPendingAddWidgetInfo) + .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE); // TODO: we need to make sure that this accounts for the options bundle. // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); startActivityForResult(intent, REQUEST_BIND_APPWIDGET); @@ -2214,21 +2316,7 @@ public class Launcher extends Activity } void processShortcut(Intent intent) { - // Handle case where user selected "Applications" - String applicationName = getResources().getString(R.string.group_applications); - String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - - if (applicationName != null && applicationName.equals(shortcutName)) { - Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); - pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); - pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application)); - Utilities.startActivityForResultSafely(this, pickIntent, REQUEST_PICK_APPLICATION); - } else { - Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT); - } + Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT); } void processWallpaper(Intent intent) { @@ -2260,12 +2348,6 @@ public class Launcher extends Activity sFolders.remove(folder.id); } - protected void startWallpaper() { - final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); - pickWallpaper.setComponent(getWallpaperPickerComponent()); - startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER); - } - protected ComponentName getWallpaperPickerComponent() { return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName()); } @@ -2369,59 +2451,63 @@ public class Launcher extends Activity Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { - // Open shortcut - final ShortcutInfo shortcut = (ShortcutInfo) tag; - final Intent intent = shortcut.intent; - - // Check for special shortcuts - if (intent.getComponent() != null) { - final String shortcutClass = intent.getComponent().getClassName(); - - if (shortcutClass.equals(WidgetAdder.class.getName())) { - onClickAddWidgetButton(); - return; - } else if (shortcutClass.equals(MemoryDumpActivity.class.getName())) { - MemoryDumpActivity.startDump(this); - return; - } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) { - toggleShowWeightWatcher(); - return; - } - } - - // Start activities - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - intent.setSourceBounds(new Rect(pos[0], pos[1], - pos[0] + v.getWidth(), pos[1] + v.getHeight())); - - boolean success = startActivitySafely(v, intent, tag); - - mStats.recordLaunch(intent, shortcut); - - if (success && v instanceof BubbleTextView) { - mWaitingForResume = (BubbleTextView) v; - mWaitingForResume.setStayPressed(true); - } + onClickAppShortcut(v); } else if (tag instanceof FolderInfo) { if (v instanceof FolderIcon) { - FolderIcon fi = (FolderIcon) v; - handleFolderClick(fi); + onClickFolderIcon(v); } } else if (v == mAllAppsButton) { - if (isAllAppsVisible()) { - showWorkspace(true); - } else { - onClickAllAppsButton(v); + onClickAllAppsButton(v); + } else if (tag instanceof AppInfo) { + startAppShortcutOrInfoActivity(v); + } else if (tag instanceof LauncherAppWidgetInfo) { + if (v instanceof PendingAppWidgetHostView) { + onClickPendingWidget((PendingAppWidgetHostView) v); } } } + public void onClickPagedViewIcon(View v) { + startAppShortcutOrInfoActivity(v); + } + public boolean onTouch(View v, MotionEvent event) { return false; } /** + * Event handler for the app widget view which has not fully restored. + */ + public void onClickPendingWidget(final PendingAppWidgetHostView v) { + final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); + if (v.isReadyForClickSetup()) { + int widgetId = info.appWidgetId; + AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId); + if (appWidgetInfo != null) { + mPendingAddWidgetInfo = appWidgetInfo; + mPendingAddInfo.copyFrom(info); + mPendingAddWidgetId = widgetId; + + AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo, + info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET); + } + } else if (info.installProgress < 0) { + // The install has not been queued + final String packageName = info.providerName.getPackageName(); + showBrokenAppInstallDialog(packageName, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); + } + }); + } else { + // Download has started. + final String packageName = info.providerName.getPackageName(); + startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); + } + } + + /** * Event handler for the search button * * @param v The view that was clicked. @@ -2467,18 +2553,180 @@ public class Launcher extends Activity * * @param v The view that was clicked. */ - public void onClickAllAppsButton(View v) { - showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false); + protected void onClickAllAppsButton(View v) { + if (LOGD) Log.d(TAG, "onClickAllAppsButton"); + if (isAllAppsVisible()) { + showWorkspace(true); + } else { + showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false); + } + } + + private void showBrokenAppInstallDialog(final String packageName, + DialogInterface.OnClickListener onSearchClickListener) { + new AlertDialog.Builder(new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault)) + .setTitle(R.string.abandoned_promises_title) + .setMessage(R.string.abandoned_promise_explanation) + .setPositiveButton(R.string.abandoned_search, onSearchClickListener) + .setNeutralButton(R.string.abandoned_clean_this, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + final UserHandleCompat user = UserHandleCompat.myUserHandle(); + mWorkspace.removeAbandonedPromise(packageName, user); + } + }) + .create().show(); + return; + } + + /** + * Event handler for an app shortcut click. + * + * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}. + */ + protected void onClickAppShortcut(final View v) { + if (LOGD) Log.d(TAG, "onClickAppShortcut"); + Object tag = v.getTag(); + if (!(tag instanceof ShortcutInfo)) { + throw new IllegalArgumentException("Input must be a Shortcut"); + } + + // Open shortcut + final ShortcutInfo shortcut = (ShortcutInfo) tag; + final Intent intent = shortcut.intent; + + // Check for special shortcuts + if (intent.getComponent() != null) { + final String shortcutClass = intent.getComponent().getClassName(); + + if (shortcutClass.equals(MemoryDumpActivity.class.getName())) { + MemoryDumpActivity.startDump(this); + return; + } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) { + toggleShowWeightWatcher(); + return; + } + } + + // Check for abandoned promise + if ((v instanceof BubbleTextView) + && shortcut.isPromise() + && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) { + showBrokenAppInstallDialog( + shortcut.getTargetComponent().getPackageName(), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + startAppShortcutOrInfoActivity(v); + } + }); + return; + } + + // Start activities + startAppShortcutOrInfoActivity(v); + } + + private void startAppShortcutOrInfoActivity(View v) { + Object tag = v.getTag(); + final ShortcutInfo shortcut; + final Intent intent; + if (tag instanceof ShortcutInfo) { + shortcut = (ShortcutInfo) tag; + intent = shortcut.intent; + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + intent.setSourceBounds(new Rect(pos[0], pos[1], + pos[0] + v.getWidth(), pos[1] + v.getHeight())); + + } else if (tag instanceof AppInfo) { + shortcut = null; + intent = ((AppInfo) tag).intent; + } else { + throw new IllegalArgumentException("Input must be a Shortcut or AppInfo"); + } + + boolean success = startActivitySafely(v, intent, tag); + mStats.recordLaunch(intent, shortcut); + + if (success && v instanceof BubbleTextView) { + mWaitingForResume = (BubbleTextView) v; + mWaitingForResume.setStayPressed(true); + } + } + + /** + * Event handler for a folder icon click. + * + * @param v The view that was clicked. Must be an instance of {@link FolderIcon}. + */ + protected void onClickFolderIcon(View v) { + if (LOGD) Log.d(TAG, "onClickFolder"); + if (!(v instanceof FolderIcon)){ + throw new IllegalArgumentException("Input must be a FolderIcon"); + } + + FolderIcon folderIcon = (FolderIcon) v; + final FolderInfo info = folderIcon.getFolderInfo(); + Folder openFolder = mWorkspace.getFolderForTag(info); + + // If the folder info reports that the associated folder is open, then verify that + // it is actually opened. There have been a few instances where this gets out of sync. + if (info.opened && openFolder == null) { + Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " + + info.screenId + " (" + info.cellX + ", " + info.cellY + ")"); + info.opened = false; + } + + if (!info.opened && !folderIcon.getFolder().isDestroyed()) { + // Close any open folder + closeFolder(); + // Open the requested folder + openFolder(folderIcon); + } else { + // Find the open folder... + int folderScreen; + if (openFolder != null) { + folderScreen = mWorkspace.getPageForView(openFolder); + // .. and close it + closeFolder(openFolder); + if (folderScreen != mWorkspace.getCurrentPage()) { + // Close any folder open on the current screen + closeFolder(); + // Pull the folder onto this screen + openFolder(folderIcon); + } + } + } } /** * Event handler for the (Add) Widgets button that appears after a long press * on the home screen. */ - protected void onClickAddWidgetButton() { + protected void onClickAddWidgetButton(View view) { + if (LOGD) Log.d(TAG, "onClickAddWidgetButton"); showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true); } + /** + * Event handler for the wallpaper picker button that appears after a long press + * on the home screen. + */ + protected void onClickWallpaperPicker(View v) { + if (LOGD) Log.d(TAG, "onClickWallpaperPicker"); + final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); + pickWallpaper.setComponent(getWallpaperPickerComponent()); + startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER); + } + + /** + * Event handler for a click on the settings button that appears after a long press + * on the home screen. + */ + protected void onClickSettingsButton(View v) { + if (LOGD) Log.d(TAG, "onClickSettingsButton"); + } + public void onTouchDownAllAppsButton(View v) { // Provide the same haptic feedback that the system offers for virtual keys. v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); @@ -2504,15 +2752,7 @@ public class Launcher extends Activity return mHapticFeedbackTouchListener; } - public void onClickAppMarketButton(View v) { - if (!DISABLE_MARKET_BUTTON) { - if (mAppMarketIntent != null) { - startActivitySafely(v, mAppMarketIntent, "app market"); - } else { - Log.e(TAG, "Invalid app market intent."); - } - } - } + public void onDragStarted(View view) {} /** * Called when the user stops interacting with the launcher. @@ -2531,17 +2771,24 @@ public class Launcher extends Activity */ protected void onInteractionBegin() {} - void startApplicationDetailsActivity(ComponentName componentName) { + void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) { String packageName = componentName.getPackageName(); - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", packageName, null)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - startActivitySafely(null, intent, "startApplicationDetailsActivity"); + try { + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); + UserManagerCompat userManager = UserManagerCompat.getInstance(this); + launcherApps.showAppDetailsForProfile(componentName, user); + } catch (SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Launcher does not have permission to launch settings"); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Unable to launch settings"); + } } // returns true if the activity was started - boolean startApplicationUninstallActivity(ComponentName componentName, int flags) { + boolean startApplicationUninstallActivity(ComponentName componentName, int flags, + UserHandleCompat user) { if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) { // System applications cannot be installed. For now, show a toast explaining that. // We may give them the option of disabling apps this way. @@ -2555,6 +2802,9 @@ public class Launcher extends Activity Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + if (user != null) { + user.addToIntent(intent, Intent.EXTRA_USER); + } startActivity(intent); return true; } @@ -2562,19 +2812,35 @@ public class Launcher extends Activity boolean startActivity(View v, Intent intent, Object tag) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { // Only launch using the new animation if the shortcut has not opted out (this is a // private contract between launcher and may be ignored in the future). boolean useLaunchAnimation = (v != null) && !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); + UserManagerCompat userManager = UserManagerCompat.getInstance(this); + + UserHandleCompat user = null; + if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) { + long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1); + user = userManager.getUserForSerialNumber(serialNumber); + } + + Bundle optsBundle = null; if (useLaunchAnimation) { - ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, - v.getMeasuredWidth(), v.getMeasuredHeight()); + ActivityOptions opts = Utilities.isLmpOrAbove() ? + ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim) : + ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); + optsBundle = opts.toBundle(); + } - startActivity(intent, opts.toBundle()); + if (user == null || user.equals(UserHandleCompat.myUserHandle())) { + // Could be launching some bookkeeping activity + startActivity(intent, optsBundle); } else { - startActivity(intent); + // TODO Component can be null when shortcuts are supported for secondary user + launcherApps.startActivityForProfile(intent.getComponent(), user, + intent.getSourceBounds(), optsBundle); } return true; } catch (SecurityException e) { @@ -2589,6 +2855,10 @@ public class Launcher extends Activity boolean startActivitySafely(View v, Intent intent, Object tag) { boolean success = false; + if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { + Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); + return false; + } try { success = startActivity(v, intent, tag); } catch (ActivityNotFoundException e) { @@ -2598,40 +2868,6 @@ public class Launcher extends Activity return success; } - private void handleFolderClick(FolderIcon folderIcon) { - final FolderInfo info = folderIcon.getFolderInfo(); - Folder openFolder = mWorkspace.getFolderForTag(info); - - // If the folder info reports that the associated folder is open, then verify that - // it is actually opened. There have been a few instances where this gets out of sync. - if (info.opened && openFolder == null) { - Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " - + info.screenId + " (" + info.cellX + ", " + info.cellY + ")"); - info.opened = false; - } - - if (!info.opened && !folderIcon.getFolder().isDestroyed()) { - // Close any open folder - closeFolder(); - // Open the requested folder - openFolder(folderIcon); - } else { - // Find the open folder... - int folderScreen; - if (openFolder != null) { - folderScreen = mWorkspace.getPageForView(openFolder); - // .. and close it - closeFolder(openFolder); - if (folderScreen != mWorkspace.getCurrentPage()) { - // Close any folder open on the current screen - closeFolder(); - // Pull the folder onto this screen - openFolder(folderIcon); - } - } - } - } - /** * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView * in the DragLayer in the exact absolute location of the original FolderIcon. @@ -2703,7 +2939,10 @@ public class Launcher extends Activity ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, scaleX, scaleY); - oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); + if (Utilities.isLmpOrAbove()) { + oa.setInterpolator(new LogDecelerateInterpolator(100, 0)); + } + oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.start(); } @@ -2720,7 +2959,7 @@ public class Launcher extends Activity copyFolderIconToImage(fi); ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, scaleX, scaleY); - oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration)); + oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -2773,9 +3012,6 @@ public class Launcher extends Activity folder.dismissEditingName(); } closeFolder(folder); - - // Dismiss the folder cling - mLauncherClings.dismissFolderCling(null); } } @@ -2808,23 +3044,22 @@ public class Launcher extends Activity } else { return false; } + } else { + return false; } } - if (!(v instanceof CellLayout)) { - v = (View) v.getParent().getParent(); - } - - resetAddInfo(); - CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); - // This happens when long clicking an item with the dpad/trackball - if (longClickCellInfo == null) { - return true; + CellLayout.CellInfo longClickCellInfo = null; + View itemUnderLongClick = null; + if (v.getTag() instanceof ItemInfo) { + ItemInfo info = (ItemInfo) v.getTag(); + longClickCellInfo = new CellLayout.CellInfo(v, info);; + itemUnderLongClick = longClickCellInfo.cell; + resetAddInfo(); } // The hotseat touch handling does not go through Workspace, and we always allow long press // on hotseat items. - final View itemUnderLongClick = longClickCellInfo.cell; final boolean inHotseat = isHotseatLayout(v); boolean allowLongPress = inHotseat || mWorkspace.allowLongPress(); if (allowLongPress && !mDragController.isDragging()) { @@ -2832,7 +3067,6 @@ public class Launcher extends Activity // User long pressed on empty space mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - // Disabling reordering until we sort out some issues. if (mWorkspace.isInOverviewMode()) { mWorkspace.startReordering(v); } else { @@ -2876,22 +3110,12 @@ public class Launcher extends Activity return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE); } - /** - * Helper method for the cameraZoomIn/cameraZoomOut animations - * @param view The view being animated - * @param scaleFactor The scale factor used for the zoom - */ - private void setPivotsForZoom(View view, float scaleFactor) { - view.setPivotX(view.getWidth() / 2.0f); - view.setPivotY(view.getHeight() / 2.0f); - } - private void setWorkspaceBackground(boolean workspace) { mLauncherView.setBackground(workspace ? mWorkspaceBackgroundDrawable : null); } - void updateWallpaperVisibility(boolean visible) { + protected void changeWallpaperVisiblity(boolean visible) { int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; int curflags = getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -2980,6 +3204,7 @@ public class Launcher extends Activity AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType(); showAppsCustomizeHelper(animated, springLoaded, contentType); } + private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded, final AppsCustomizePagedView.ContentType contentType) { if (mStateAnimation != null) { @@ -2987,98 +3212,178 @@ public class Launcher extends Activity mStateAnimation.cancel(); mStateAnimation = null; } + + boolean material = Utilities.isLmpOrAbove(); + final Resources res = getResources(); final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime); final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime); + final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime); + final int itemsAlphaStagger = + res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger); + final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); final View fromView = mWorkspace; final AppsCustomizeTabHost toView = mAppsCustomizeTabHost; - final int startDelay = - res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger); - setPivotsForZoom(toView, scale); + final ArrayList<View> layerViews = new ArrayList<View>(); - // Shrink workspaces away if going to AppsCustomize from workspace + Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ? + Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN; Animator workspaceAnim = - mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated); + mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews); if (!LauncherAppState.isDisableAllApps() || contentType == AppsCustomizePagedView.ContentType.Widgets) { // Set the content type for the all apps/widgets space mAppsCustomizeTabHost.setContentTypeImmediate(contentType); } - if (animated) { - toView.setScaleX(scale); - toView.setScaleY(scale); - final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView); - scaleAnim. - scaleX(1f).scaleY(1f). - setDuration(duration). - setInterpolator(new Workspace.ZoomOutInterpolator()); - - toView.setVisibility(View.VISIBLE); - toView.setAlpha(0f); - final ObjectAnimator alphaAnim = LauncherAnimUtils - .ofFloat(toView, "alpha", 0f, 1f) - .setDuration(fadeDuration); - alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f)); - alphaAnim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - if (animation == null) { - throw new RuntimeException("animation is null"); - } - float t = (Float) animation.getAnimatedValue(); - dispatchOnLauncherTransitionStep(fromView, t); - dispatchOnLauncherTransitionStep(toView, t); - } - }); + // If for some reason our views aren't initialized, don't animate + boolean initialized = getAllAppsButton() != null; - // toView should appear right at the end of the workspace shrink - // animation + if (animated && initialized) { mStateAnimation = LauncherAnimUtils.createAnimatorSet(); - mStateAnimation.play(scaleAnim).after(startDelay); - mStateAnimation.play(alphaAnim).after(startDelay); + final AppsCustomizePagedView content = (AppsCustomizePagedView) + toView.findViewById(R.id.apps_customize_pane_content); + + final View page = content.getPageAt(content.getCurrentPage()); + final View revealView = toView.findViewById(R.id.fake_page); + + final float initialPanelAlpha = 1f; + + final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets; + if (isWidgetTray) { + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); + } else { + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); + } + + // Hide the real page background, and swap in the fake one + content.setPageBackgroundsVisible(false); + revealView.setVisibility(View.VISIBLE); + // We need to hide this view as the animation start will be posted. + revealView.setAlpha(0); + + int width = revealView.getMeasuredWidth(); + int height = revealView.getMeasuredHeight(); + float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); + + revealView.setTranslationY(0); + revealView.setTranslationX(0); + + // Get the y delta between the center of the page and the center of the all apps button + int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, + getAllAppsButton(), null); + + float alpha = 0; + float xDrift = 0; + float yDrift = 0; + if (material) { + alpha = isWidgetTray ? 0.3f : 1f; + yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1]; + xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0]; + } else { + yDrift = 2 * height / 3; + xDrift = 0; + } + final float initAlpha = alpha; + + revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + layerViews.add(revealView); + PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f); + PropertyValuesHolder panelDriftY = + PropertyValuesHolder.ofFloat("translationY", yDrift, 0); + PropertyValuesHolder panelDriftX = + PropertyValuesHolder.ofFloat("translationX", xDrift, 0); + + ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, + panelAlpha, panelDriftY, panelDriftX); + + panelAlphaAndDrift.setDuration(revealDuration); + panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + mStateAnimation.play(panelAlphaAndDrift); + + if (page != null) { + page.setVisibility(View.VISIBLE); + page.setLayerType(View.LAYER_TYPE_HARDWARE, null); + layerViews.add(page); + + ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0); + page.setTranslationY(yDrift); + pageDrift.setDuration(revealDuration); + pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); + pageDrift.setStartDelay(itemsAlphaStagger); + mStateAnimation.play(pageDrift); + + page.setAlpha(0f); + ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f); + itemsAlpha.setDuration(revealDuration); + itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + itemsAlpha.setStartDelay(itemsAlphaStagger); + mStateAnimation.play(itemsAlpha); + } + + View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator); + pageIndicators.setAlpha(0.01f); + ObjectAnimator indicatorsAlpha = + ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f); + indicatorsAlpha.setDuration(revealDuration); + mStateAnimation.play(indicatorsAlpha); + + if (material) { + final View allApps = getAllAppsButton(); + int allAppsButtonSize = LauncherAppState.getInstance(). + getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize; + float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2; + Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2, + height / 2, startRadius, revealRadius); + reveal.setDuration(revealDuration); + reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + reveal.addListener(new AnimatorListenerAdapter() { + public void onAnimationStart(Animator animation) { + if (!isWidgetTray) { + allApps.setVisibility(View.INVISIBLE); + } + } + public void onAnimationEnd(Animator animation) { + if (!isWidgetTray) { + allApps.setVisibility(View.VISIBLE); + } + } + }); + mStateAnimation.play(reveal); + } mStateAnimation.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationStart(Animator animation) { - // Prepare the position - toView.setTranslationX(0.0f); - toView.setTranslationY(0.0f); - toView.setVisibility(View.VISIBLE); - toView.bringToFront(); - } - @Override public void onAnimationEnd(Animator animation) { dispatchOnLauncherTransitionEnd(fromView, animated, false); dispatchOnLauncherTransitionEnd(toView, animated, false); + revealView.setVisibility(View.INVISIBLE); + revealView.setLayerType(View.LAYER_TYPE_NONE, null); + if (page != null) { + page.setLayerType(View.LAYER_TYPE_NONE, null); + } + content.setPageBackgroundsVisible(true); + // Hide the search bar if (mSearchDropTargetBar != null) { mSearchDropTargetBar.hideSearchBar(false); } } + }); if (workspaceAnim != null) { mStateAnimation.play(workspaceAnim); } - boolean delayAnim = false; - dispatchOnLauncherTransitionPrepare(fromView, animated, false); dispatchOnLauncherTransitionPrepare(toView, animated, false); - - // If any of the objects being animated haven't been measured/laid out - // yet, delay the animation until we get a layout pass - if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) || - (mWorkspace.getMeasuredWidth() == 0) || - (toView.getMeasuredWidth() == 0)) { - delayAnim = true; - } - final AnimatorSet stateAnimation = mStateAnimation; final Runnable startAnimRunnable = new Runnable() { public void run() { @@ -3086,23 +3391,28 @@ public class Launcher extends Activity // we waited for a layout/draw pass if (mStateAnimation != stateAnimation) return; - setPivotsForZoom(toView, scale); dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); - LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView); + + revealView.setAlpha(initAlpha); + if (Utilities.isLmpOrAbove()) { + for (int i = 0; i < layerViews.size(); i++) { + View v = layerViews.get(i); + if (v != null) { + boolean attached = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + attached = v.isAttachedToWindow(); + } + if (attached) v.buildLayer(); + } + } + } + mStateAnimation.start(); } }; - if (delayAnim) { - final ViewTreeObserver observer = toView.getViewTreeObserver(); - observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { - public void onGlobalLayout() { - startAnimRunnable.run(); - toView.getViewTreeObserver().removeOnGlobalLayoutListener(this); - } - }); - } else { - startAnimRunnable.run(); - } + toView.bringToFront(); + toView.setVisibility(View.VISIBLE); + toView.post(startAnimRunnable); } else { toView.setTranslationX(0.0f); toView.setTranslationY(0.0f); @@ -3139,54 +3449,184 @@ public class Launcher extends Activity mStateAnimation.cancel(); mStateAnimation = null; } + + boolean material = Utilities.isLmpOrAbove(); Resources res = getResources(); final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime); - final int fadeOutDuration = - res.getInteger(R.integer.config_appsCustomizeFadeOutTime); + final int fadeOutDuration = res.getInteger(R.integer.config_appsCustomizeFadeOutTime); + final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime); + final int itemsAlphaStagger = + res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger); + final float scaleFactor = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor); final View fromView = mAppsCustomizeTabHost; final View toView = mWorkspace; Animator workspaceAnim = null; + final ArrayList<View> layerViews = new ArrayList<View>(); + if (toState == Workspace.State.NORMAL) { - int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger); workspaceAnim = mWorkspace.getChangeStateAnimation( - toState, animated, stagger, -1); + toState, animated, layerViews); } else if (toState == Workspace.State.SPRING_LOADED || toState == Workspace.State.OVERVIEW) { workspaceAnim = mWorkspace.getChangeStateAnimation( - toState, animated); - } - - setPivotsForZoom(fromView, scaleFactor); - showHotseat(animated); - if (animated) { - final LauncherViewPropertyAnimator scaleAnim = - new LauncherViewPropertyAnimator(fromView); - scaleAnim. - scaleX(scaleFactor).scaleY(scaleFactor). - setDuration(duration). - setInterpolator(new Workspace.ZoomInInterpolator()); - - final ObjectAnimator alphaAnim = LauncherAnimUtils - .ofFloat(fromView, "alpha", 1f, 0f) - .setDuration(fadeOutDuration); - alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator()); - alphaAnim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = 1f - (Float) animation.getAnimatedValue(); - dispatchOnLauncherTransitionStep(fromView, t); - dispatchOnLauncherTransitionStep(toView, t); - } - }); + toState, animated, layerViews); + } + + // If for some reason our views aren't initialized, don't animate + boolean initialized = getAllAppsButton() != null; + if (animated && initialized) { mStateAnimation = LauncherAnimUtils.createAnimatorSet(); + if (workspaceAnim != null) { + mStateAnimation.play(workspaceAnim); + } - dispatchOnLauncherTransitionPrepare(fromView, animated, true); - dispatchOnLauncherTransitionPrepare(toView, animated, true); - mAppsCustomizeContent.stopScrolling(); + final AppsCustomizePagedView content = (AppsCustomizePagedView) + fromView.findViewById(R.id.apps_customize_pane_content); + + final View page = content.getPageAt(content.getNextPage()); + + // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases + int count = content.getChildCount(); + for (int i = 0; i < count; i++) { + View child = content.getChildAt(i); + if (child != page) { + child.setVisibility(View.INVISIBLE); + } + } + final View revealView = fromView.findViewById(R.id.fake_page); + + // hideAppsCustomizeHelper is called in some cases when it is already hidden + // don't perform all these no-op animations. In particularly, this was causing + // the all-apps button to pop in and out. + if (fromView.getVisibility() == View.VISIBLE) { + AppsCustomizePagedView.ContentType contentType = content.getContentType(); + final boolean isWidgetTray = + contentType == AppsCustomizePagedView.ContentType.Widgets; + + if (isWidgetTray) { + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); + } else { + revealView.setBackground(res.getDrawable(R.drawable.quantum_panel)); + } + + int width = revealView.getMeasuredWidth(); + int height = revealView.getMeasuredHeight(); + float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4); + + // Hide the real page background, and swap in the fake one + revealView.setVisibility(View.VISIBLE); + content.setPageBackgroundsVisible(false); + + final View allAppsButton = getAllAppsButton(); + revealView.setTranslationY(0); + int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, + allAppsButton, null); + + float xDrift = 0; + float yDrift = 0; + if (material) { + yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1]; + xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0]; + } else { + yDrift = 5 * height / 4; + xDrift = 0; + } + + revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + TimeInterpolator decelerateInterpolator = material ? + new LogDecelerateInterpolator(100, 0) : + new LogDecelerateInterpolator(30, 0); + + // The vertical motion of the apps panel should be delayed by one frame + // from the conceal animation in order to give the right feel. We correpsondingly + // shorten the duration so that the slide and conceal end at the same time. + ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY", + 0, yDrift); + panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); + panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); + panelDriftY.setInterpolator(decelerateInterpolator); + mStateAnimation.play(panelDriftY); + + ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX", + 0, xDrift); + panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); + panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); + panelDriftX.setInterpolator(decelerateInterpolator); + mStateAnimation.play(panelDriftX); + + if (isWidgetTray || !material) { + float finalAlpha = material ? 0.4f : 0f; + revealView.setAlpha(1f); + ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha", + 1f, finalAlpha); + panelAlpha.setDuration(revealDuration); + panelAlpha.setInterpolator(material ? decelerateInterpolator : + new AccelerateInterpolator(1.5f)); + mStateAnimation.play(panelAlpha); + } + + if (page != null) { + page.setLayerType(View.LAYER_TYPE_HARDWARE, null); + + ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY", + 0, yDrift); + page.setTranslationY(0); + pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); + pageDrift.setInterpolator(decelerateInterpolator); + pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); + mStateAnimation.play(pageDrift); + + page.setAlpha(1f); + ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f); + itemsAlpha.setDuration(100); + itemsAlpha.setInterpolator(decelerateInterpolator); + mStateAnimation.play(itemsAlpha); + } + + View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator); + pageIndicators.setAlpha(1f); + ObjectAnimator indicatorsAlpha = + LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f); + indicatorsAlpha.setDuration(revealDuration); + indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f)); + mStateAnimation.play(indicatorsAlpha); + + width = revealView.getMeasuredWidth(); + + if (material) { + if (!isWidgetTray) { + allAppsButton.setVisibility(View.INVISIBLE); + } + int allAppsButtonSize = LauncherAppState.getInstance(). + getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize; + float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2; + Animator reveal = + LauncherAnimUtils.createCircularReveal(revealView, width / 2, + height / 2, revealRadius, finalRadius); + reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); + reveal.setDuration(revealDuration); + reveal.setStartDelay(itemsAlphaStagger); + + reveal.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + revealView.setVisibility(View.INVISIBLE); + if (!isWidgetTray) { + allAppsButton.setVisibility(View.VISIBLE); + } + } + }); + + mStateAnimation.play(reveal); + } + + dispatchOnLauncherTransitionPrepare(fromView, animated, true); + dispatchOnLauncherTransitionPrepare(toView, animated, true); + mAppsCustomizeContent.stopScrolling(); + } mStateAnimation.addListener(new AnimatorListenerAdapter() { @Override @@ -3197,17 +3637,57 @@ public class Launcher extends Activity if (onCompleteRunnable != null) { onCompleteRunnable.run(); } + + revealView.setLayerType(View.LAYER_TYPE_NONE, null); + if (page != null) { + page.setLayerType(View.LAYER_TYPE_NONE, null); + } + content.setPageBackgroundsVisible(true); + // Unhide side pages + int count = content.getChildCount(); + for (int i = 0; i < count; i++) { + View child = content.getChildAt(i); + child.setVisibility(View.VISIBLE); + } + + // Reset page transforms + if (page != null) { + page.setTranslationX(0); + page.setTranslationY(0); + page.setAlpha(1); + } + content.setCurrentPage(content.getNextPage()); + mAppsCustomizeContent.updateCurrentPageScroll(); } }); - mStateAnimation.playTogether(scaleAnim, alphaAnim); - if (workspaceAnim != null) { - mStateAnimation.play(workspaceAnim); - } - dispatchOnLauncherTransitionStart(fromView, animated, true); - dispatchOnLauncherTransitionStart(toView, animated, true); - LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView); + final AnimatorSet stateAnimation = mStateAnimation; + final Runnable startAnimRunnable = new Runnable() { + public void run() { + // Check that mStateAnimation hasn't changed while + // we waited for a layout/draw pass + if (mStateAnimation != stateAnimation) + return; + dispatchOnLauncherTransitionStart(fromView, animated, false); + dispatchOnLauncherTransitionStart(toView, animated, false); + + if (Utilities.isLmpOrAbove()) { + for (int i = 0; i < layerViews.size(); i++) { + View v = layerViews.get(i); + if (v != null) { + boolean attached = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + attached = v.isAttachedToWindow(); + } + if (attached) v.buildLayer(); + } + } + } + mStateAnimation.start(); + } + }; + fromView.post(startAnimRunnable); } else { fromView.setVisibility(View.GONE); dispatchOnLauncherTransitionPrepare(fromView, animated, true); @@ -3236,10 +3716,7 @@ public class Launcher extends Activity } void showWorkspace(boolean animated, Runnable onCompleteRunnable) { - if (mWorkspace.isInOverviewMode()) { - mWorkspace.exitOverviewMode(animated); - } - if (mState != State.WORKSPACE) { + if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) { boolean wasInSpringLoadedMode = (mState != State.WORKSPACE); mWorkspace.setVisibility(View.VISIBLE); hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable); @@ -3288,7 +3765,13 @@ public class Launcher extends Activity mAppsCustomizeTabHost.reset(); } showAppsCustomizeHelper(animated, false, contentType); - mAppsCustomizeTabHost.requestFocus(); + mAppsCustomizeTabHost.post(new Runnable() { + @Override + public void run() { + // We post this in-case the all apps view isn't yet constructed. + mAppsCustomizeTabHost.requestFocus(); + } + }); // Change the state *after* we've called all the transition code mState = State.APPS_CUSTOMIZE; @@ -3328,7 +3811,6 @@ public class Launcher extends Activity } } }, delay); - } void exitSpringLoadedDragMode() { @@ -3350,25 +3832,6 @@ public class Launcher extends Activity } /** - * Shows the hotseat area. - */ - void showHotseat(boolean animated) { - if (!LauncherAppState.getInstance().isScreenLarge()) { - if (animated) { - if (mHotseat.getAlpha() != 1f) { - int duration = 0; - if (mSearchDropTargetBar != null) { - duration = mSearchDropTargetBar.getTransitionInDuration(); - } - mHotseat.animate().alpha(1f).setDuration(duration); - } - } else { - mHotseat.setAlpha(1f); - } - } - } - - /** * Hides the hotseat area. */ void hideHotseat(boolean animated) { @@ -3621,44 +4084,6 @@ public class Launcher extends Activity public void disableVoiceButtonProxy(boolean disabled) { updateVoiceButtonProxyVisible(disabled); } - /** - * Sets the app market icon - */ - private void updateAppMarketIcon() { - if (!DISABLE_MARKET_BUTTON) { - final View marketButton = findViewById(R.id.market_button); - Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET); - // Find the app market activity by resolving an intent. - // (If multiple app markets are installed, it will return the ResolverActivity.) - ComponentName activityName = intent.resolveActivity(getPackageManager()); - if (activityName != null) { - int coi = getCurrentOrientationIndexForGlobalIcons(); - mAppMarketIntent = intent; - sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity( - R.id.market_button, activityName, R.drawable.ic_launcher_market_holo, - TOOLBAR_ICON_METADATA_NAME); - marketButton.setVisibility(View.VISIBLE); - } else { - // We should hide and disable the view so that we don't try and restore the visibility - // of it when we swap between drag & normal states from IconDropTarget subclasses. - marketButton.setVisibility(View.GONE); - marketButton.setEnabled(false); - } - } - } - - private void updateAppMarketIcon(Drawable.ConstantState d) { - if (!DISABLE_MARKET_BUTTON) { - // Ensure that the new drawable we are creating has the approprate toolbar icon bounds - Resources r = getResources(); - Drawable marketIconDrawable = d.newDrawable(r); - int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width); - int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height); - marketIconDrawable.setBounds(0, 0, w, h); - - updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable); - } - } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { @@ -3667,7 +4092,7 @@ public class Launcher extends Activity text.clear(); // Populate event with a fake title based on the current state. if (mState == State.APPS_CUSTOMIZE) { - text.add(mAppsCustomizeTabHost.getCurrentTabView().getContentDescription()); + text.add(mAppsCustomizeTabHost.getContentTag()); } else { text.add(getString(R.string.all_apps_home_button_label)); } @@ -3775,7 +4200,7 @@ public class Launcher extends Activity * Implementation of the method from LauncherModel.Callbacks. */ public void startBinding() { - mWorkspaceLoading = true; + setWorkspaceLoading(true); // If we're starting binding all over again, clear any bind calls we'd postponed in // the past (see waitUntilResume) -- we don't need them since we're starting binding @@ -3875,7 +4300,7 @@ public class Launcher extends Activity } // Remove the extra empty screen - mWorkspace.removeExtraEmptyScreen(false, null); + mWorkspace.removeExtraEmptyScreen(false, false); if (!LauncherAppState.isDisableAllApps() && addedApps != null && mAppsCustomizeContent != null) { @@ -3926,7 +4351,15 @@ public class Launcher extends Activity if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { CellLayout cl = mWorkspace.getScreenWithId(item.screenId); if (cl != null && cl.isOccupied(item.cellX, item.cellY)) { - throw new RuntimeException("OCCUPIED"); + View v = cl.getChildAt(item.cellX, item.cellY); + Object tag = v.getTag(); + String desc = "Collision while binding workspace item: " + item + + ". Collides with " + tag; + if (LauncherAppState.isDogfoodBuild()) { + throw (new RuntimeException(desc)); + } else { + Log.d(TAG, desc); + } } } @@ -4021,13 +4454,74 @@ public class Launcher extends Activity } final Workspace workspace = mWorkspace; - final int appWidgetId = item.appWidgetId; - final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); + AppWidgetProviderInfo appWidgetInfo; + if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) && + ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) { + + appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName); + if (appWidgetInfo == null) { + if (DEBUG_WIDGETS) { + Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + + " belongs to component " + item.providerName + + ", as the povider is null"); + } + LauncherModel.deleteItemFromDatabase(this, item); + return; + } + // Note: This assumes that the id remap broadcast is received before this step. + // If that is not the case, the id remap will be ignored and user may see the + // click to setup view. + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null); + pendingInfo.spanX = item.spanX; + pendingInfo.spanY = item.spanY; + pendingInfo.minSpanX = item.minSpanX; + pendingInfo.minSpanY = item.minSpanY; + Bundle options = + AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo); + + int newWidgetId = mAppWidgetHost.allocateAppWidgetId(); + boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed( + newWidgetId, appWidgetInfo, options); + + // TODO consider showing a permission dialog when the widget is clicked. + if (!success) { + mAppWidgetHost.deleteAppWidgetId(newWidgetId); + if (DEBUG_WIDGETS) { + Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + + " belongs to component " + item.providerName + + ", as the launcher is unable to bing a new widget id"); + } + LauncherModel.deleteItemFromDatabase(this, item); + return; + } + + item.appWidgetId = newWidgetId; + + // If the widget has a configure activity, it is still needs to set it up, otherwise + // the widget is ready to go. + item.restoreStatus = (appWidgetInfo.configure == null) + ? LauncherAppWidgetInfo.RESTORE_COMPLETED + : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + + LauncherModel.updateItemInDatabase(this, item); } - item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { + final int appWidgetId = item.appWidgetId; + appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + if (DEBUG_WIDGETS) { + Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); + } + + item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + } else { + appWidgetInfo = null; + PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item); + view.updateIcon(mIconCache); + item.hostView = view; + item.hostView.updateAppWidget(null); + item.hostView.setOnClickListener(this); + } item.hostView.setTag(item); item.onBindAppWidget(this); @@ -4044,6 +4538,26 @@ public class Launcher extends Activity } } + /** + * Restores a pending widget. + * + * @param appWidgetId The app widget id + * @param cellInfo The position on screen where to create the widget. + */ + private void completeRestoreAppWidget(final int appWidgetId) { + LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId); + if ((view == null) || !(view instanceof PendingAppWidgetHostView)) { + Log.e(TAG, "Widget update called, when the widget no longer exists."); + return; + } + + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); + info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; + + mWorkspace.reinflateWidgetsIfNecessary(); + LauncherModel.updateItemInDatabase(this, info); + } + public void onPageBoundSynchronously(int page) { mSynchronouslyBoundPages.add(page); } @@ -4071,24 +4585,44 @@ public class Launcher extends Activity mWorkspace.restoreInstanceStateForRemainingPages(); + setWorkspaceLoading(false); + sendLoadingCompleteBroadcastIfNecessary(); + // If we received the result of any pending adds while the loader was running (e.g. the // widget configuration forced an orientation change), process them now. - for (int i = 0; i < sPendingAddList.size(); i++) { - completeAdd(sPendingAddList.get(i)); - } - sPendingAddList.clear(); + if (sPendingAddItem != null) { + final long screenId = completeAdd(sPendingAddItem); - // Update the market app icon as necessary (the other icons will be managed in response to - // package changes in bindSearchablesChanged() - if (!DISABLE_MARKET_BUTTON) { - updateAppMarketIcon(); + // TODO: this moves the user to the page where the pending item was added. Ideally, + // the screen would be guaranteed to exist after bind, and the page would be set through + // the workspace restore process. + mWorkspace.post(new Runnable() { + @Override + public void run() { + mWorkspace.snapToScreenId(screenId); + } + }); + sPendingAddItem = null; } - mWorkspaceLoading = false; if (upgradePath) { mWorkspace.getUniqueComponents(true, null); mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null); } + PackageInstallerCompat.getInstance(this).onFinishBind(); + mModel.recheckRestoredItems(this); + } + + private void sendLoadingCompleteBroadcastIfNecessary() { + if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) { + String permission = + getResources().getString(R.string.receive_first_load_broadcast_permission); + Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE); + sendBroadcast(intent, permission); + SharedPreferences.Editor editor = mSharedPrefs.edit(); + editor.putBoolean(FIRST_LOAD_COMPLETE, true); + editor.apply(); + } } public boolean isAllAppsButtonRank(int rank) { @@ -4176,7 +4710,7 @@ public class Launcher extends Activity } if (mWorkspace != null) { - mWorkspace.updateShortcuts(apps); + mWorkspace.updateShortcutsAndWidgets(apps); } if (!LauncherAppState.isDisableAllApps() && @@ -4186,6 +4720,48 @@ public class Launcher extends Activity } /** + * Packages were restored + */ + public void bindAppsRestored(final ArrayList<AppInfo> apps) { + Runnable r = new Runnable() { + public void run() { + bindAppsRestored(apps); + } + }; + if (waitUntilResume(r)) { + return; + } + + if (mWorkspace != null) { + mWorkspace.updateShortcutsAndWidgets(apps); + } + } + + /** + * Update the state of a package, typically related to install state. + * + * Implementation of the method from LauncherModel.Callbacks. + */ + @Override + public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) { + if (mWorkspace != null) { + mWorkspace.updatePackageState(installInfo); + } + } + + /** + * Update the label and icon of all the icons in a package + * + * Implementation of the method from LauncherModel.Callbacks. + */ + @Override + public void updatePackageBadge(String packageName) { + if (mWorkspace != null) { + mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle()); + } + } + + /** * A package was uninstalled. We take both the super set of packageNames * in addition to specific applications to remove, the reason being that * this can be called when a package is updated as well. In that scenario, @@ -4195,10 +4771,10 @@ public class Launcher extends Activity * Implementation of the method from LauncherModel.Callbacks. */ public void bindComponentsRemoved(final ArrayList<String> packageNames, - final ArrayList<AppInfo> appInfos) { + final ArrayList<AppInfo> appInfos, final UserHandleCompat user) { Runnable r = new Runnable() { public void run() { - bindComponentsRemoved(packageNames, appInfos); + bindComponentsRemoved(packageNames, appInfos, user); } }; if (waitUntilResume(r)) { @@ -4206,10 +4782,10 @@ public class Launcher extends Activity } if (!packageNames.isEmpty()) { - mWorkspace.removeItemsByPackageName(packageNames); + mWorkspace.removeItemsByPackageName(packageNames, user); } if (!appInfos.isEmpty()) { - mWorkspace.removeItemsByApplicationInfo(appInfos); + mWorkspace.removeItemsByApplicationInfo(appInfos, user); } // Notify the drag controller @@ -4307,7 +4883,7 @@ public class Launcher extends Activity * @param hint the hint to be displayed in the search bar. */ protected void onSearchBarHintChanged(String hint) { - mLauncherClings.updateSearchBarHint(hint); + } protected boolean isLauncherPreinstalled() { @@ -4325,6 +4901,17 @@ public class Launcher extends Activity } } + /** + * This method indicates whether or not we should suggest default wallpaper dimensions + * when our wallpaper cropper was not yet used to set a wallpaper. + */ + protected boolean overrideWallpaperDimensions() { + return true; + } + + protected boolean shouldClingFocusHotseatApp() { + return false; + } protected String getFirstRunClingSearchBarHint() { return ""; } @@ -4347,23 +4934,19 @@ public class Launcher extends Activity return ""; } - public void dismissFirstRunCling(View v) { - mLauncherClings.dismissFirstRunCling(v); - } - public void dismissMigrationClingCopyApps(View v) { - mLauncherClings.dismissMigrationClingCopyApps(v); - } - public void dismissMigrationClingUseDefault(View v) { - mLauncherClings.dismissMigrationClingUseDefault(v); - } - public void dismissMigrationWorkspaceCling(View v) { - mLauncherClings.dismissMigrationWorkspaceCling(v); - } - public void dismissWorkspaceCling(View v) { - mLauncherClings.dismissWorkspaceCling(v); + /** + * To be overridden by subclasses to indicate that there is an activity to launch + * before showing the standard launcher experience. + */ + protected boolean hasFirstRunActivity() { + return false; } - public void dismissFolderCling(View v) { - mLauncherClings.dismissFolderCling(v); + + /** + * To be overridden by subclasses to launch any first run activity + */ + protected Intent getFirstRunActivity() { + return null; } private boolean shouldRunFirstRunActivity() { @@ -4371,15 +4954,21 @@ public class Launcher extends Activity !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); } - public void showFirstRunActivity() { + protected boolean hasRunFirstRunActivity() { + return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); + } + + public boolean showFirstRunActivity() { if (shouldRunFirstRunActivity() && hasFirstRunActivity()) { Intent firstRunIntent = getFirstRunActivity(); if (firstRunIntent != null) { startActivity(firstRunIntent); markFirstRunActivityShown(); + return true; } } + return false; } private void markFirstRunActivityShown() { @@ -4388,6 +4977,77 @@ public class Launcher extends Activity editor.apply(); } + /** + * To be overridden by subclasses to indicate that there is an in-activity full-screen intro + * screen that must be displayed and dismissed. + */ + protected boolean hasDismissableIntroScreen() { + return false; + } + + /** + * Full screen intro screen to be shown and dismissed before the launcher can be used. + */ + protected View getIntroScreen() { + return null; + } + + /** + * To be overriden by subclasses to indicate whether the in-activity intro screen has been + * dismissed. This method is ignored if #hasDismissableIntroScreen returns false. + */ + private boolean shouldShowIntroScreen() { + return hasDismissableIntroScreen() && + !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false); + } + + protected void showIntroScreen() { + View introScreen = getIntroScreen(); + changeWallpaperVisiblity(false); + if (introScreen != null) { + mDragLayer.showOverlayView(introScreen); + } + } + + public void dismissIntroScreen() { + markIntroScreenDismissed(); + if (showFirstRunActivity()) { + // We delay hiding the intro view until the first run activity is showing. This + // avoids a blip. + mWorkspace.postDelayed(new Runnable() { + @Override + public void run() { + mDragLayer.dismissOverlayView(); + showFirstRunClings(); + } + }, ACTIVITY_START_DELAY); + } else { + mDragLayer.dismissOverlayView(); + showFirstRunClings(); + } + changeWallpaperVisiblity(true); + } + + private void markIntroScreenDismissed() { + SharedPreferences.Editor editor = mSharedPrefs.edit(); + editor.putBoolean(INTRO_SCREEN_DISMISSED, true); + editor.apply(); + } + + private void showFirstRunClings() { + // The two first run cling paths are mutually exclusive, if the launcher is preinstalled + // on the device, then we always show the first run cling experience (or if there is no + // launcher2). Otherwise, we prompt the user upon started for migration + LauncherClings launcherClings = new LauncherClings(this); + if (launcherClings.shouldShowFirstRunOrMigrationClings()) { + if (mModel.canMigrateFromOldLauncherDb(this)) { + launcherClings.showMigrationCling(); + } else { + launcherClings.showLongPressCling(true); + } + } + } + void showWorkspaceSearchAndHotseat() { if (mWorkspace != null) mWorkspace.setAlpha(1f); if (mHotseat != null) mHotseat.setAlpha(1f); @@ -4402,24 +5062,44 @@ public class Launcher extends Activity if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false); } - public ItemInfo createAppDragInfo(Intent appLaunchIntent) { - ResolveInfo ri = getPackageManager().resolveActivity(appLaunchIntent, 0); - if (ri == null) { + // Called from search suggestion, not supported in other profiles. + final UserHandleCompat myUser = UserHandleCompat.myUserHandle(); + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this); + LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent, + myUser); + if (activityInfo == null) { return null; } - return new AppInfo(getPackageManager(), ri, mIconCache, null); + return new AppInfo(this, activityInfo, myUser, mIconCache, null); } public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption, Bitmap icon) { - return new ShortcutInfo(shortcutIntent, caption, icon); + // Called from search suggestion, not supported in other profiles. + return createShortcutDragInfo(shortcutIntent, caption, icon, + UserHandleCompat.myUserHandle()); + } + + public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption, + Bitmap icon, UserHandleCompat user) { + UserManagerCompat userManager = UserManagerCompat.getInstance(this); + CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user); + return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user); + } + + protected void moveWorkspaceToDefaultScreen() { + mWorkspace.moveToDefaultScreen(false); } public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) { dragView.setTag(dragInfo); - mWorkspace.onDragStartedWithItem(dragView); - mWorkspace.beginDragShared(dragView, source); + mWorkspace.onExternalDragStartedWithItem(dragView); + mWorkspace.beginExternalDragShared(dragView, source); + } + + @Override + public void onPageSwitch(View newPage, int newPageIndex) { } /** |