94b510cc68
1) Adding DragOptions to easily extend drap functionality 2) Changing onDragStarted signature to send more information 3) Updating states for dropTargetButton based on drag event directly 4) Removing folder item based on onDragStarted and not startDrag Change-Id: I65b684e092ddc081d086bfe2c8c1973ed170eaeb
4595 lines
176 KiB
Java
4595 lines
176 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3;
|
|
|
|
import android.Manifest;
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.ValueAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.annotation.TargetApi;
|
|
import android.app.Activity;
|
|
import android.app.ActivityOptions;
|
|
import android.app.AlertDialog;
|
|
import android.app.SearchManager;
|
|
import android.appwidget.AppWidgetHostView;
|
|
import android.appwidget.AppWidgetManager;
|
|
import android.appwidget.AppWidgetProviderInfo;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentCallbacks2;
|
|
import android.content.ComponentName;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.ContextWrapper;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.IntentSender;
|
|
import android.content.SharedPreferences;
|
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Configuration;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.AsyncTask;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.StrictMode;
|
|
import android.os.SystemClock;
|
|
import android.os.Trace;
|
|
import android.os.UserHandle;
|
|
import android.text.Selection;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.text.TextUtils;
|
|
import android.text.method.TextKeyListener;
|
|
import android.util.Log;
|
|
import android.view.Display;
|
|
import android.view.HapticFeedbackConstants;
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MotionEvent;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.view.View.OnLongClickListener;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewTreeObserver;
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
import android.view.animation.OvershootInterpolator;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.Advanceable;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.android.launcher3.DropTarget.DragObject;
|
|
import com.android.launcher3.LauncherSettings.Favorites;
|
|
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
|
import com.android.launcher3.allapps.AllAppsContainerView;
|
|
import com.android.launcher3.allapps.AllAppsTransitionController;
|
|
import com.android.launcher3.allapps.DefaultAppSearchController;
|
|
import com.android.launcher3.compat.AppWidgetManagerCompat;
|
|
import com.android.launcher3.compat.LauncherActivityInfoCompat;
|
|
import com.android.launcher3.compat.LauncherAppsCompat;
|
|
import com.android.launcher3.compat.UserHandleCompat;
|
|
import com.android.launcher3.compat.UserManagerCompat;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.config.ProviderConfig;
|
|
import com.android.launcher3.dragndrop.DragController;
|
|
import com.android.launcher3.dragndrop.DragLayer;
|
|
import com.android.launcher3.dragndrop.DragOptions;
|
|
import com.android.launcher3.dragndrop.DragView;
|
|
import com.android.launcher3.dynamicui.ExtractedColors;
|
|
import com.android.launcher3.folder.Folder;
|
|
import com.android.launcher3.folder.FolderIcon;
|
|
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
|
|
import com.android.launcher3.logging.FileLog;
|
|
import com.android.launcher3.logging.UserEventDispatcher;
|
|
import com.android.launcher3.model.WidgetsModel;
|
|
import com.android.launcher3.pageindicators.PageIndicator;
|
|
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
|
import com.android.launcher3.shortcuts.DeepShortcutsContainer;
|
|
import com.android.launcher3.shortcuts.ShortcutKey;
|
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
|
import com.android.launcher3.util.ComponentKey;
|
|
import com.android.launcher3.util.ItemInfoMatcher;
|
|
import com.android.launcher3.util.MultiHashMap;
|
|
import com.android.launcher3.util.PackageManagerHelper;
|
|
import com.android.launcher3.util.TestingUtils;
|
|
import com.android.launcher3.util.Thunk;
|
|
import com.android.launcher3.util.ViewOnDrawExecutor;
|
|
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
|
import com.android.launcher3.widget.WidgetHostViewLoader;
|
|
import com.android.launcher3.widget.WidgetsContainerView;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Default launcher application.
|
|
*/
|
|
public class Launcher extends Activity
|
|
implements LauncherExterns, View.OnClickListener, OnLongClickListener,
|
|
LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
|
|
AccessibilityManager.AccessibilityStateChangeListener {
|
|
public static final String TAG = "Launcher";
|
|
static final boolean LOGD = false;
|
|
|
|
static final boolean DEBUG_WIDGETS = false;
|
|
static final boolean DEBUG_STRICT_MODE = false;
|
|
static final boolean DEBUG_RESUME_TIME = false;
|
|
|
|
private static final int REQUEST_CREATE_SHORTCUT = 1;
|
|
private static final int REQUEST_CREATE_APPWIDGET = 5;
|
|
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_BIND_PENDING_APPWIDGET = 14;
|
|
private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
|
|
|
|
private static final int REQUEST_PERMISSION_CALL_PHONE = 13;
|
|
|
|
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
|
|
|
|
/**
|
|
* IntentStarter uses request codes starting with this. This must be greater than all activity
|
|
* request codes used internally.
|
|
*/
|
|
protected static final int REQUEST_LAST = 100;
|
|
|
|
// To turn on these properties, type
|
|
// adb shell setprop logTap.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
|
|
static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
|
|
|
|
// The Intent extra that defines whether to ignore the launch animation
|
|
static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
|
|
"com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
|
|
|
|
public static final String ACTION_APPWIDGET_HOST_RESET =
|
|
"com.android.launcher3.intent.ACTION_APPWIDGET_HOST_RESET";
|
|
|
|
// Type: int
|
|
private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
|
|
// Type: int
|
|
private static final String RUNTIME_STATE = "launcher.state";
|
|
// Type: Content Values / parcelable
|
|
private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item";
|
|
// Type: parcelable
|
|
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
|
|
// Type: parcelable
|
|
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
|
|
|
|
static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
|
|
|
|
/** The different states that Launcher can be in. */
|
|
enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
|
|
WIDGETS, WIDGETS_SPRING_LOADED }
|
|
|
|
@Thunk State mState = State.WORKSPACE;
|
|
@Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
|
|
|
|
private boolean mIsSafeModeEnabled;
|
|
|
|
static final int APPWIDGET_HOST_ID = 1024;
|
|
public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 500;
|
|
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
|
|
private static final int ACTIVITY_START_DELAY = 1000;
|
|
|
|
// How long to wait before the new-shortcut animation automatically pans the workspace
|
|
private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
|
|
private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
|
|
@Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
|
|
|
|
private final BroadcastReceiver mUiBroadcastReceiver = new BroadcastReceiver() {
|
|
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
|
|
closeSystemDialogs();
|
|
} else if (ACTION_APPWIDGET_HOST_RESET.equals(intent.getAction())) {
|
|
if (mAppWidgetHost != null) {
|
|
mAppWidgetHost.startListening();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
@Thunk Workspace mWorkspace;
|
|
private View mLauncherView;
|
|
@Thunk DragLayer mDragLayer;
|
|
private DragController mDragController;
|
|
private View mQsbContainer;
|
|
|
|
public View mWeightWatcher;
|
|
|
|
private AppWidgetManagerCompat mAppWidgetManager;
|
|
private LauncherAppWidgetHost mAppWidgetHost;
|
|
|
|
@Thunk final ItemInfo mPendingAddInfo = new ItemInfo();
|
|
private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
|
|
private int mPendingAddWidgetId = -1;
|
|
|
|
private int[] mTmpAddItemCellCoordinates = new int[2];
|
|
|
|
@Thunk Hotseat mHotseat;
|
|
private ViewGroup mOverviewPanel;
|
|
|
|
private View mAllAppsButton;
|
|
private View mWidgetsButton;
|
|
|
|
private DropTargetBar mDropTargetBar;
|
|
|
|
// Main container view for the all apps screen.
|
|
@Thunk AllAppsContainerView mAppsView;
|
|
AllAppsTransitionController mAllAppsController;
|
|
|
|
// Main container view and the model for the widget tray screen.
|
|
@Thunk WidgetsContainerView mWidgetsView;
|
|
@Thunk WidgetsModel mWidgetsModel;
|
|
|
|
private Bundle mSavedState;
|
|
// We set the state in both onCreate and then onNewIntent in some cases, which causes both
|
|
// scroll issues (because the workspace may not have been measured yet) and extra work.
|
|
// Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
|
|
private State mOnResumeState = State.NONE;
|
|
|
|
private SpannableStringBuilder mDefaultKeySsb = null;
|
|
|
|
@Thunk boolean mWorkspaceLoading = true;
|
|
|
|
private boolean mPaused = true;
|
|
private boolean mRestoring;
|
|
private boolean mWaitingForResult;
|
|
private boolean mOnResumeNeedsLoad;
|
|
|
|
private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
|
|
private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
|
|
private ViewOnDrawExecutor mPendingExecutor;
|
|
|
|
private LauncherModel mModel;
|
|
private IconCache mIconCache;
|
|
private ExtractedColors mExtractedColors;
|
|
private LauncherAccessibilityDelegate mAccessibilityDelegate;
|
|
private boolean mIsResumeFromActionScreenOff;
|
|
@Thunk boolean mUserPresent = true;
|
|
private boolean mVisible = false;
|
|
private boolean mHasFocus = false;
|
|
private boolean mAttached = false;
|
|
|
|
/** Maps launcher activity components to their list of shortcut ids. */
|
|
private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
|
|
|
|
private View.OnTouchListener mHapticFeedbackTouchListener;
|
|
|
|
// Related to the auto-advancing of widgets
|
|
private final int ADVANCE_MSG = 1;
|
|
private static final int ADVANCE_INTERVAL = 20000;
|
|
private static final int ADVANCE_STAGGER = 250;
|
|
|
|
private boolean mAutoAdvanceRunning = false;
|
|
private long mAutoAdvanceSentTime;
|
|
private long mAutoAdvanceTimeLeft = -1;
|
|
@Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
|
|
|
|
// Determines how long to wait after a rotation before restoring the screen orientation to
|
|
// match the sensor state.
|
|
private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
|
|
|
|
private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
|
|
private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
|
|
|
|
// We only want to get the SharedPreferences once since it does an FS stat each time we get
|
|
// it from the context.
|
|
private SharedPreferences mSharedPrefs;
|
|
|
|
// Holds the page that we need to animate to, and the icon views that we need to animate up
|
|
// when we scroll to that page on resume.
|
|
@Thunk ImageView mFolderIconImageView;
|
|
private Bitmap mFolderIconBitmap;
|
|
private Canvas mFolderIconCanvas;
|
|
private Rect mRectForFolderAnimation = new Rect();
|
|
|
|
private DeviceProfile mDeviceProfile;
|
|
|
|
private boolean mMoveToDefaultScreenFromNewIntent;
|
|
|
|
// This is set to the view that launched the activity that navigated the user away from
|
|
// launcher. Since there is no callback for when the activity has finished launching, enable
|
|
// the press state and keep this reference to reset the press state when we return to launcher.
|
|
private BubbleTextView mWaitingForResume;
|
|
|
|
protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
|
|
new HashMap<String, CustomAppWidget>();
|
|
|
|
static {
|
|
if (TestingUtils.ENABLE_CUSTOM_WIDGET_TEST) {
|
|
TestingUtils.addDummyWidget(sCustomAppWidgets);
|
|
}
|
|
}
|
|
|
|
// Exiting spring loaded mode happens with a delay. This runnable object triggers the
|
|
// state transition. If another state transition happened during this delay,
|
|
// simply unregister this runnable.
|
|
private Runnable mExitSpringLoadedModeRunnable;
|
|
|
|
@Thunk Runnable mBuildLayersRunnable = new Runnable() {
|
|
public void run() {
|
|
if (mWorkspace != null) {
|
|
mWorkspace.buildPageHardwareLayers();
|
|
}
|
|
}
|
|
};
|
|
|
|
private static PendingAddArguments sPendingAddItem;
|
|
|
|
@Thunk static class PendingAddArguments {
|
|
int requestCode;
|
|
Intent intent;
|
|
long container;
|
|
long screenId;
|
|
int cellX;
|
|
int cellY;
|
|
int appWidgetId;
|
|
}
|
|
|
|
private UserEventDispatcher mUserEventDispatcher;
|
|
|
|
public ViewGroupFocusHelper mFocusHandler;
|
|
private boolean mRotationEnabled = false;
|
|
|
|
@Thunk void setOrientation() {
|
|
if (mRotationEnabled) {
|
|
unlockScreenOrientation(true);
|
|
} else {
|
|
setRequestedOrientation(
|
|
ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
|
|
}
|
|
}
|
|
|
|
private RotationPrefChangeHandler mRotationPrefChangeHandler;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
if (DEBUG_STRICT_MODE) {
|
|
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
|
.detectDiskReads()
|
|
.detectDiskWrites()
|
|
.detectNetwork() // or .detectAll() for all detectable problems
|
|
.penaltyLog()
|
|
.build());
|
|
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
|
.detectLeakedSqlLiteObjects()
|
|
.detectLeakedClosableObjects()
|
|
.penaltyLog()
|
|
.penaltyDeath()
|
|
.build());
|
|
}
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.beginSection("Launcher-onCreate");
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.preOnCreate();
|
|
}
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
LauncherAppState app = LauncherAppState.getInstance();
|
|
|
|
// Load configuration-specific DeviceProfile
|
|
mDeviceProfile = getResources().getConfiguration().orientation
|
|
== Configuration.ORIENTATION_LANDSCAPE ?
|
|
app.getInvariantDeviceProfile().landscapeProfile
|
|
: app.getInvariantDeviceProfile().portraitProfile;
|
|
|
|
mSharedPrefs = Utilities.getPrefs(this);
|
|
mIsSafeModeEnabled = getPackageManager().isSafeMode();
|
|
mModel = app.setLauncher(this);
|
|
mIconCache = app.getIconCache();
|
|
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
|
|
|
|
mDragController = new DragController(this);
|
|
mAllAppsController = new AllAppsTransitionController(this);
|
|
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
|
|
|
|
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
|
|
|
|
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
|
|
mAppWidgetHost.startListening();
|
|
|
|
// If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
|
|
// this also ensures that any synchronous binding below doesn't re-trigger another
|
|
// LauncherModel load.
|
|
mPaused = false;
|
|
|
|
setContentView(R.layout.launcher);
|
|
|
|
setupViews();
|
|
mDeviceProfile.layout(this, false /* notifyListeners */);
|
|
mExtractedColors = new ExtractedColors();
|
|
loadExtractedColorsAndColorItems();
|
|
|
|
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
|
|
.addAccessibilityStateChangeListener(this);
|
|
|
|
lockAllApps();
|
|
|
|
mSavedState = savedInstanceState;
|
|
restoreState(mSavedState);
|
|
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.endSection();
|
|
}
|
|
|
|
if (!mRestoring) {
|
|
if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
|
|
// If the user leaves launcher, then we should just load items asynchronously when
|
|
// they return.
|
|
mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
|
|
} else {
|
|
// We only load the page synchronously if the user rotates (or triggers a
|
|
// configuration change) while launcher is in the foreground
|
|
if (!mModel.startLoader(mWorkspace.getRestorePage())) {
|
|
// If we are not binding synchronously, show a fade in animation when
|
|
// the first page bind completes.
|
|
mDragLayer.setAlpha(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For handling default keys
|
|
mDefaultKeySsb = new SpannableStringBuilder();
|
|
Selection.setSelection(mDefaultKeySsb, 0);
|
|
|
|
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
|
|
filter.addAction(ACTION_APPWIDGET_HOST_RESET);
|
|
registerReceiver(mUiBroadcastReceiver, filter);
|
|
|
|
mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
|
|
// In case we are on a device with locked rotation, we should look at preferences to check
|
|
// if the user has specifically allowed rotation.
|
|
if (!mRotationEnabled) {
|
|
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
|
|
mRotationPrefChangeHandler = new RotationPrefChangeHandler();
|
|
mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
|
|
}
|
|
|
|
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
|
|
// we want the screen to auto-rotate based on the current orientation
|
|
setOrientation();
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onCreate(savedInstanceState);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onExtractedColorsChanged() {
|
|
loadExtractedColorsAndColorItems();
|
|
}
|
|
|
|
private void loadExtractedColorsAndColorItems() {
|
|
// TODO: do this in pre-N as well, once the extraction part is complete.
|
|
if (mExtractedColors != null && Utilities.isNycOrAbove()) {
|
|
mExtractedColors.load(this);
|
|
mHotseat.updateColor(mExtractedColors, !mPaused);
|
|
mWorkspace.getPageIndicator().updateColor(mExtractedColors);
|
|
}
|
|
}
|
|
|
|
private LauncherCallbacks mLauncherCallbacks;
|
|
|
|
public void onPostCreate(Bundle savedInstanceState) {
|
|
super.onPostCreate(savedInstanceState);
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onPostCreate(savedInstanceState);
|
|
}
|
|
}
|
|
|
|
public void onInsetsChanged(Rect insets) {
|
|
mDeviceProfile.updateInsets(insets);
|
|
mDeviceProfile.layout(this, true /* notifyListeners */);
|
|
}
|
|
|
|
/**
|
|
* Call this after onCreate to set or clear overlay.
|
|
*/
|
|
public void setLauncherOverlay(LauncherOverlay overlay) {
|
|
if (overlay != null) {
|
|
overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl());
|
|
}
|
|
mWorkspace.setLauncherOverlay(overlay);
|
|
}
|
|
|
|
public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
|
|
mLauncherCallbacks = callbacks;
|
|
mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() {
|
|
private boolean mWorkspaceImportanceStored = false;
|
|
private boolean mHotseatImportanceStored = false;
|
|
private int mWorkspaceImportanceForAccessibility =
|
|
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
|
|
private int mHotseatImportanceForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
|
|
|
|
@Override
|
|
public void onSearchOverlayOpened() {
|
|
if (mWorkspaceImportanceStored || mHotseatImportanceStored) {
|
|
return;
|
|
}
|
|
// The underlying workspace and hotseat are temporarily suppressed by the search
|
|
// overlay. So they shouldn't be accessible.
|
|
if (mWorkspace != null) {
|
|
mWorkspaceImportanceForAccessibility =
|
|
mWorkspace.getImportantForAccessibility();
|
|
mWorkspace.setImportantForAccessibility(
|
|
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
|
mWorkspaceImportanceStored = true;
|
|
}
|
|
if (mHotseat != null) {
|
|
mHotseatImportanceForAccessibility = mHotseat.getImportantForAccessibility();
|
|
mHotseat.setImportantForAccessibility(
|
|
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
|
mHotseatImportanceStored = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSearchOverlayClosed() {
|
|
if (mWorkspaceImportanceStored && mWorkspace != null) {
|
|
mWorkspace.setImportantForAccessibility(mWorkspaceImportanceForAccessibility);
|
|
}
|
|
if (mHotseatImportanceStored && mHotseat != null) {
|
|
mHotseat.setImportantForAccessibility(mHotseatImportanceForAccessibility);
|
|
}
|
|
mWorkspaceImportanceStored = false;
|
|
mHotseatImportanceStored = false;
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onLauncherProviderChange() {
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onLauncherProviderChange();
|
|
}
|
|
}
|
|
|
|
/** To be overridden by subclasses to hint to Launcher that we have custom content */
|
|
protected boolean hasCustomContentToLeft() {
|
|
if (mLauncherCallbacks != null) {
|
|
return mLauncherCallbacks.hasCustomContentToLeft();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* To be overridden by subclasses to populate the custom content container and call
|
|
* {@link #addToCustomContentPage}. This will only be invoked if
|
|
* {@link #hasCustomContentToLeft()} is {@code true}.
|
|
*/
|
|
protected void populateCustomContentContainer() {
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.populateCustomContentContainer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
|
|
* ensure the custom content page is added or removed if necessary.
|
|
*/
|
|
protected void invalidateHasCustomContentToLeft() {
|
|
if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
|
|
// Not bound yet, wait for bindScreens to be called.
|
|
return;
|
|
}
|
|
|
|
if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
|
|
// Create the custom content page and call the subclass to populate it.
|
|
mWorkspace.createCustomContentContainer();
|
|
populateCustomContentContainer();
|
|
} else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
|
|
mWorkspace.removeCustomContentPage();
|
|
}
|
|
}
|
|
|
|
public UserEventDispatcher getUserEventDispatcher() {
|
|
if (mLauncherCallbacks != null) {
|
|
UserEventDispatcher dispatcher = mLauncherCallbacks.getUserEventDispatcher();
|
|
if (dispatcher != null) {
|
|
return dispatcher;
|
|
}
|
|
}
|
|
|
|
// Logger object is a singleton and does not have to be coupled with the foreground
|
|
// activity. Since most user event logging is done on the UI, the object is retrieved
|
|
// from the callback for convenience.
|
|
if (mUserEventDispatcher == null) {
|
|
mUserEventDispatcher = new UserEventDispatcher();
|
|
}
|
|
return mUserEventDispatcher;
|
|
}
|
|
|
|
public boolean isDraggingEnabled() {
|
|
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
|
|
// that is subsequently removed from the workspace in startBinding().
|
|
return !isWorkspaceLoading();
|
|
}
|
|
|
|
public int getViewIdForItem(ItemInfo info) {
|
|
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
|
|
// This cast is safe as long as the id < 0x00FFFFFF
|
|
// Since we jail all the dynamically generated views, there should be no clashes
|
|
// with any other views.
|
|
return (int) info.id;
|
|
}
|
|
|
|
/**
|
|
* 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 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_CREATE_SHORTCUT:
|
|
completeAddShortcut(args.intent, args.container, screenId, args.cellX,
|
|
args.cellY);
|
|
break;
|
|
case REQUEST_CREATE_APPWIDGET:
|
|
completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
|
|
break;
|
|
case REQUEST_RECONFIGURE_APPWIDGET:
|
|
completeRestoreAppWidget(args.appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
|
|
break;
|
|
case REQUEST_BIND_PENDING_APPWIDGET: {
|
|
int widgetId = args.appWidgetId;
|
|
LauncherAppWidgetInfo info =
|
|
completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
|
|
if (info != null) {
|
|
// Since the view was just bound, also launch the configure activity if needed
|
|
LauncherAppWidgetProviderInfo provider = mAppWidgetManager
|
|
.getLauncherAppWidgetInfo(widgetId);
|
|
if (provider != null && provider.configure != null) {
|
|
startRestoredWidgetReconfigActivity(provider, info);
|
|
}
|
|
}
|
|
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 screenId;
|
|
}
|
|
|
|
private void handleActivityResult(
|
|
final int requestCode, final int resultCode, final Intent data) {
|
|
// Reset the startActivity waiting flag
|
|
setWaitingForResult(false);
|
|
final int pendingAddWidgetId = mPendingAddWidgetId;
|
|
mPendingAddWidgetId = -1;
|
|
|
|
Runnable exitSpringLoaded = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
|
|
EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
}
|
|
};
|
|
|
|
if (requestCode == REQUEST_BIND_APPWIDGET) {
|
|
// This is called only if the user did not previously have permissions to bind widgets
|
|
final int appWidgetId = data != null ?
|
|
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
|
|
if (resultCode == RESULT_CANCELED) {
|
|
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
|
|
mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
|
|
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
|
|
} else if (resultCode == RESULT_OK) {
|
|
addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
|
|
mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
|
|
}
|
|
return;
|
|
} else if (requestCode == REQUEST_PICK_WALLPAPER) {
|
|
if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
|
|
// User could have free-scrolled between pages before picking a wallpaper; make sure
|
|
// we move to the closest one now.
|
|
mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen());
|
|
showWorkspace(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
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;
|
|
int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
|
: -1;
|
|
if (widgetId < 0) {
|
|
appWidgetId = pendingAddWidgetId;
|
|
} else {
|
|
appWidgetId = widgetId;
|
|
}
|
|
|
|
final int result;
|
|
if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
|
|
Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
|
|
"returned from the widget configuration activity.");
|
|
result = RESULT_CANCELED;
|
|
completeTwoStageWidgetDrop(result, appWidgetId);
|
|
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 {
|
|
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
|
|
|| requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
|
|
if (resultCode == RESULT_OK) {
|
|
// Update the widget view.
|
|
PendingAddArguments args = preparePendingAddArgs(requestCode, data,
|
|
pendingAddWidgetId, mPendingAddInfo);
|
|
if (workspaceLocked) {
|
|
sPendingAddItem = args;
|
|
} else {
|
|
completeAdd(args);
|
|
}
|
|
}
|
|
// Leave the widget in the pending state if the user canceled the configure.
|
|
return;
|
|
}
|
|
|
|
if (requestCode == REQUEST_CREATE_SHORTCUT) {
|
|
// Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
|
|
if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
|
|
final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
|
|
mPendingAddInfo);
|
|
if (isWorkspaceLocked()) {
|
|
sPendingAddItem = args;
|
|
} else {
|
|
completeAdd(args);
|
|
mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
|
|
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
|
|
}
|
|
} else if (resultCode == RESULT_CANCELED) {
|
|
mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
|
|
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
|
|
}
|
|
}
|
|
mDragLayer.clearAnimatedView();
|
|
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(
|
|
final int requestCode, final int resultCode, final Intent data) {
|
|
handleActivityResult(requestCode, resultCode, data);
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
|
|
}
|
|
}
|
|
|
|
/** @Override for MNC */
|
|
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
|
int[] grantResults) {
|
|
if (requestCode == REQUEST_PERMISSION_CALL_PHONE && sPendingAddItem != null
|
|
&& sPendingAddItem.requestCode == REQUEST_PERMISSION_CALL_PHONE) {
|
|
View v = null;
|
|
CellLayout layout = getCellLayout(sPendingAddItem.container, sPendingAddItem.screenId);
|
|
if (layout != null) {
|
|
v = layout.getChildAt(sPendingAddItem.cellX, sPendingAddItem.cellY);
|
|
}
|
|
Intent intent = sPendingAddItem.intent;
|
|
sPendingAddItem = null;
|
|
if (grantResults.length > 0
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
startActivitySafely(v, intent, null);
|
|
} else {
|
|
// TODO: Show a snack bar with link to settings
|
|
Toast.makeText(this, getString(R.string.msg_no_phone_permission,
|
|
getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions,
|
|
grantResults);
|
|
}
|
|
}
|
|
|
|
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 = 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;
|
|
}
|
|
}
|
|
|
|
@Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
|
|
CellLayout cellLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
|
|
Runnable onCompleteRunnable = null;
|
|
int animationType = 0;
|
|
|
|
AppWidgetHostView boundWidget = null;
|
|
if (resultCode == RESULT_OK) {
|
|
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
|
|
final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
|
|
mPendingAddWidgetInfo);
|
|
boundWidget = layout;
|
|
onCompleteRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
|
|
mPendingAddInfo.screenId, layout, null);
|
|
exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
|
|
EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
|
|
}
|
|
};
|
|
} else if (resultCode == RESULT_CANCELED) {
|
|
mAppWidgetHost.deleteAppWidgetId(appWidgetId);
|
|
animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
|
|
}
|
|
if (mDragLayer.getAnimatedView() != null) {
|
|
mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
|
|
(DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
|
|
animationType, boundWidget, true);
|
|
} else if (onCompleteRunnable != null) {
|
|
// The animated view may be null in the case of a rotation during widget configuration
|
|
onCompleteRunnable.run();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onStop() {
|
|
super.onStop();
|
|
FirstFrameAnimatorHelper.setIsVisible(false);
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onStop();
|
|
}
|
|
|
|
if (Utilities.isNycMR1OrAbove()) {
|
|
mAppWidgetHost.stopListening();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onStart() {
|
|
super.onStart();
|
|
FirstFrameAnimatorHelper.setIsVisible(true);
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onStart();
|
|
}
|
|
|
|
if (Utilities.isNycMR1OrAbove()) {
|
|
mAppWidgetHost.startListening();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
long startTime = 0;
|
|
if (DEBUG_RESUME_TIME) {
|
|
startTime = System.currentTimeMillis();
|
|
Log.v(TAG, "Launcher.onResume()");
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.preOnResume();
|
|
}
|
|
|
|
super.onResume();
|
|
getUserEventDispatcher().resetElapsedSessionMillis();
|
|
|
|
// Restore the previous launcher state
|
|
if (mOnResumeState == State.WORKSPACE) {
|
|
showWorkspace(false);
|
|
} else if (mOnResumeState == State.APPS) {
|
|
boolean launchedFromApp = (mWaitingForResume != null);
|
|
// Don't update the predicted apps if the user is returning to launcher in the apps
|
|
// view after launching an app, as they may be depending on the UI to be static to
|
|
// switch to another app, otherwise, if it was
|
|
showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */,
|
|
false /* focusSearchBar */);
|
|
} else if (mOnResumeState == State.WIDGETS) {
|
|
showWidgetsView(false, false);
|
|
}
|
|
mOnResumeState = State.NONE;
|
|
|
|
mPaused = false;
|
|
if (mRestoring || mOnResumeNeedsLoad) {
|
|
setWorkspaceLoading(true);
|
|
mModel.startLoader(getCurrentWorkspaceScreen());
|
|
mRestoring = false;
|
|
mOnResumeNeedsLoad = false;
|
|
}
|
|
if (mBindOnResumeCallbacks.size() > 0) {
|
|
// We might have postponed some bind calls until onResume (see waitUntilResume) --
|
|
// execute them here
|
|
long startTimeCallbacks = 0;
|
|
if (DEBUG_RESUME_TIME) {
|
|
startTimeCallbacks = System.currentTimeMillis();
|
|
}
|
|
|
|
for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
|
|
mBindOnResumeCallbacks.get(i).run();
|
|
}
|
|
mBindOnResumeCallbacks.clear();
|
|
if (DEBUG_RESUME_TIME) {
|
|
Log.d(TAG, "Time spent processing callbacks in onResume: " +
|
|
(System.currentTimeMillis() - startTimeCallbacks));
|
|
}
|
|
}
|
|
if (mOnResumeCallbacks.size() > 0) {
|
|
for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
|
|
mOnResumeCallbacks.get(i).run();
|
|
}
|
|
mOnResumeCallbacks.clear();
|
|
}
|
|
|
|
// Reset the pressed state of icons that were locked in the press state while activities
|
|
// were launching
|
|
if (mWaitingForResume != null) {
|
|
// Resets the previous workspace icon press state
|
|
mWaitingForResume.setStayPressed(false);
|
|
}
|
|
|
|
// 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
|
|
// (framework issue). On resuming, we ensure that any widgets are inflated for the current
|
|
// orientation.
|
|
if (!isWorkspaceLoading()) {
|
|
getWorkspace().reinflateWidgetsIfNecessary();
|
|
}
|
|
|
|
if (DEBUG_RESUME_TIME) {
|
|
Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
|
|
}
|
|
|
|
// We want to suppress callbacks about CustomContent being shown if we have just received
|
|
// onNewIntent while the user was present within launcher. In that case, we post a call
|
|
// to move the user to the main screen (which will occur after onResume). We don't want to
|
|
// have onHide (from onPause), then onShow, then onHide again, which we get if we don't
|
|
// suppress here.
|
|
if (mWorkspace.getCustomContentCallbacks() != null
|
|
&& !mMoveToDefaultScreenFromNewIntent) {
|
|
// If we are resuming and the custom content is the current page, we call onShow().
|
|
// It is also possible 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(true);
|
|
}
|
|
}
|
|
mMoveToDefaultScreenFromNewIntent = false;
|
|
updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
|
|
mWorkspace.onResume();
|
|
|
|
if (!isWorkspaceLoading()) {
|
|
// Process any items that were added while Launcher was away.
|
|
InstallShortcutReceiver.disableAndFlushInstallQueue(this);
|
|
|
|
// Refresh shortcuts if the permission changed.
|
|
mModel.refreshShortcutsIfRequired();
|
|
}
|
|
|
|
if (shouldShowDiscoveryBounce()) {
|
|
mAllAppsController.showDiscoveryBounce();
|
|
}
|
|
mIsResumeFromActionScreenOff = false;
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onResume();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
// Ensure that items added to Launcher are queued until Launcher returns
|
|
InstallShortcutReceiver.enableInstallQueue();
|
|
|
|
super.onPause();
|
|
mPaused = true;
|
|
mDragController.cancelDrag();
|
|
mDragController.resetLastGestureUpTime();
|
|
|
|
// We call onHide() aggressively. The custom content callbacks should be able to
|
|
// debounce excess onHide calls.
|
|
if (mWorkspace.getCustomContentCallbacks() != null) {
|
|
mWorkspace.getCustomContentCallbacks().onHide();
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onPause();
|
|
}
|
|
}
|
|
|
|
public interface CustomContentCallbacks {
|
|
// 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();
|
|
}
|
|
|
|
public interface LauncherOverlay {
|
|
|
|
/**
|
|
* Touch interaction leading to overscroll has begun
|
|
*/
|
|
public void onScrollInteractionBegin();
|
|
|
|
/**
|
|
* Touch interaction related to overscroll has ended
|
|
*/
|
|
public void onScrollInteractionEnd();
|
|
|
|
/**
|
|
* Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
|
|
* screen (or in the case of RTL, the rightmost screen).
|
|
*/
|
|
public void onScrollChange(float progress, boolean rtl);
|
|
|
|
/**
|
|
* Called when the launcher is ready to use the overlay
|
|
* @param callbacks A set of callbacks provided by Launcher in relation to the overlay
|
|
*/
|
|
public void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
|
|
}
|
|
|
|
public interface LauncherSearchCallbacks {
|
|
/**
|
|
* Called when the search overlay is shown.
|
|
*/
|
|
public void onSearchOverlayOpened();
|
|
|
|
/**
|
|
* Called when the search overlay is dismissed.
|
|
*/
|
|
public void onSearchOverlayClosed();
|
|
}
|
|
|
|
public interface LauncherOverlayCallbacks {
|
|
public void onScrollChanged(float progress);
|
|
}
|
|
|
|
class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
|
|
|
|
public void onScrollChanged(float progress) {
|
|
if (mWorkspace != null) {
|
|
mWorkspace.onOverlayScrollChanged(progress);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean hasSettings() {
|
|
if (mLauncherCallbacks != null) {
|
|
return mLauncherCallbacks.hasSettings();
|
|
} else {
|
|
// On devices with a locked orientation, we will at least have the allow rotation
|
|
// setting.
|
|
return !getResources().getBoolean(R.bool.allow_rotation);
|
|
}
|
|
}
|
|
|
|
public void addToCustomContentPage(View customContent,
|
|
CustomContentCallbacks callbacks, String description) {
|
|
mWorkspace.addToCustomContentPage(customContent, callbacks, description);
|
|
}
|
|
|
|
// The custom content needs to offset its content to account for the QSB
|
|
public int getTopOffsetForCustomContent() {
|
|
return mWorkspace.getPaddingTop();
|
|
}
|
|
|
|
@Override
|
|
public Object onRetainNonConfigurationInstance() {
|
|
// Flag the loader to stop early before switching
|
|
if (mModel.isCurrentCallbacks(this)) {
|
|
mModel.stopLoader();
|
|
}
|
|
//TODO(hyunyoungs): stop the widgets loader when there is a rotation.
|
|
|
|
return Boolean.TRUE;
|
|
}
|
|
|
|
// We can't hide the IME if it was forced open. So don't bother
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
mHasFocus = hasFocus;
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onWindowFocusChanged(hasFocus);
|
|
}
|
|
}
|
|
|
|
private boolean acceptFilter() {
|
|
final InputMethodManager inputManager = (InputMethodManager)
|
|
getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
return !inputManager.isFullscreenMode();
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
final int uniChar = event.getUnicodeChar();
|
|
final boolean handled = super.onKeyDown(keyCode, event);
|
|
final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
|
|
if (!handled && acceptFilter() && isKeyNotWhitespace) {
|
|
boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
|
|
keyCode, event);
|
|
if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
|
|
// something usable has been typed - start a search
|
|
// the typed text will be retrieved and cleared by
|
|
// showSearchDialog()
|
|
// If there are multiple keystrokes before the search dialog takes focus,
|
|
// onSearchRequested() will be called for every keystroke,
|
|
// but it is idempotent, so it's fine.
|
|
return onSearchRequested();
|
|
}
|
|
}
|
|
|
|
// Eat the long press event so the keyboard doesn't come up.
|
|
if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
|
|
return true;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
// Ignore the menu key if we are currently dragging or are on the custom content screen
|
|
if (!isOnCustomContent() && !mDragController.isDragging()) {
|
|
// Close any open folders
|
|
closeFolder();
|
|
|
|
// Close any shortcuts containers
|
|
closeShortcutsContainer();
|
|
|
|
// Stop resizing any widgets
|
|
mWorkspace.exitWidgetResizeMode();
|
|
|
|
// Show the overview mode if we are on the workspace
|
|
if (mState == State.WORKSPACE && !mWorkspace.isInOverviewMode() &&
|
|
!mWorkspace.isSwitchingState()) {
|
|
mOverviewPanel.requestFocus();
|
|
showOverviewMode(true, true /* requestButtonFocus */);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
|
|
private String getTypedText() {
|
|
return mDefaultKeySsb.toString();
|
|
}
|
|
|
|
private void clearTypedText() {
|
|
mDefaultKeySsb.clear();
|
|
mDefaultKeySsb.clearSpans();
|
|
Selection.setSelection(mDefaultKeySsb, 0);
|
|
}
|
|
|
|
/**
|
|
* Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
|
|
* State
|
|
*/
|
|
private static State intToState(int stateOrdinal) {
|
|
State state = State.WORKSPACE;
|
|
final State[] stateValues = State.values();
|
|
for (int i = 0; i < stateValues.length; i++) {
|
|
if (stateValues[i].ordinal() == stateOrdinal) {
|
|
state = stateValues[i];
|
|
break;
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Restores the previous state, if it exists.
|
|
*
|
|
* @param savedState The previous state.
|
|
*/
|
|
private void restoreState(Bundle savedState) {
|
|
if (savedState == null) {
|
|
return;
|
|
}
|
|
|
|
State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
|
|
if (state == State.APPS || state == State.WIDGETS) {
|
|
mOnResumeState = state;
|
|
}
|
|
|
|
int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
|
|
PagedView.INVALID_RESTORE_PAGE);
|
|
if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
|
|
mWorkspace.setRestorePage(currentScreen);
|
|
}
|
|
|
|
ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM);
|
|
if (itemValues != null) {
|
|
mPendingAddInfo.readFromValues(itemValues);
|
|
AppWidgetProviderInfo info = savedState.getParcelable(
|
|
RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
|
|
mPendingAddWidgetInfo = info == null ?
|
|
null : LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
|
|
|
|
mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
|
|
setWaitingForResult(true);
|
|
mRestoring = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds all the views we need and configure them properly.
|
|
*/
|
|
private void setupViews() {
|
|
mLauncherView = findViewById(R.id.launcher);
|
|
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
|
|
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
|
|
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
|
|
mQsbContainer = mDragLayer.findViewById(mDeviceProfile.isVerticalBarLayout()
|
|
? R.id.workspace_blocked_row : R.id.qsb_container);
|
|
mWorkspace.initParentViews(mDragLayer);
|
|
|
|
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
|
|
|
// Setup the drag layer
|
|
mDragLayer.setup(this, mDragController, mAllAppsController);
|
|
|
|
// Setup the hotseat
|
|
mHotseat = (Hotseat) findViewById(R.id.hotseat);
|
|
if (mHotseat != null) {
|
|
mHotseat.setOnLongClickListener(this);
|
|
}
|
|
|
|
// Setup the overview panel
|
|
setupOverviewPanel();
|
|
|
|
// Setup the workspace
|
|
mWorkspace.setHapticFeedbackEnabled(false);
|
|
mWorkspace.setOnLongClickListener(this);
|
|
mWorkspace.setup(mDragController);
|
|
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
|
|
// default state, otherwise we will update to the wrong offsets in RTL
|
|
mWorkspace.lockWallpaperToDefaultPage();
|
|
mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
|
|
mDragController.addDragListener(mWorkspace);
|
|
|
|
// Get the search/delete/uninstall bar
|
|
mDropTargetBar = (DropTargetBar) mDragLayer.findViewById(R.id.drop_target_bar);
|
|
|
|
// Setup Apps and Widgets
|
|
mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
|
|
mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
|
|
if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
|
|
mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
|
|
} else {
|
|
mAppsView.setSearchBarController(new DefaultAppSearchController());
|
|
}
|
|
|
|
// Setup the drag controller (drop targets have to be added in reverse order in priority)
|
|
mDragController.setDragScoller(mWorkspace);
|
|
mDragController.setScrollView(mDragLayer);
|
|
mDragController.setMoveTarget(mWorkspace);
|
|
mDragController.addDropTarget(mWorkspace);
|
|
mDropTargetBar.setup(mDragController);
|
|
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
|
mAllAppsController.setupViews(mAppsView, mHotseat, mWorkspace);
|
|
}
|
|
|
|
if (TestingUtils.MEMORY_DUMP_ENABLED) {
|
|
TestingUtils.addWeightWatcher(this);
|
|
}
|
|
}
|
|
|
|
private void setupOverviewPanel() {
|
|
mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
|
|
|
|
// Long-clicking buttons in the overview panel does the same thing as clicking them.
|
|
OnLongClickListener performClickOnLongClick = new OnLongClickListener() {
|
|
@Override
|
|
public boolean onLongClick(View v) {
|
|
return v.performClick();
|
|
}
|
|
};
|
|
|
|
// Bind wallpaper button actions
|
|
View wallpaperButton = findViewById(R.id.wallpaper_button);
|
|
wallpaperButton.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View view) {
|
|
if (!mWorkspace.isSwitchingState()) {
|
|
onClickWallpaperPicker(view);
|
|
}
|
|
}
|
|
});
|
|
wallpaperButton.setOnLongClickListener(performClickOnLongClick);
|
|
wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
|
|
|
|
// Bind widget button actions
|
|
mWidgetsButton = findViewById(R.id.widget_button);
|
|
mWidgetsButton.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View view) {
|
|
if (!mWorkspace.isSwitchingState()) {
|
|
onClickAddWidgetButton(view);
|
|
}
|
|
}
|
|
});
|
|
mWidgetsButton.setOnLongClickListener(performClickOnLongClick);
|
|
mWidgetsButton.setOnTouchListener(getHapticFeedbackTouchListener());
|
|
|
|
// Bind settings actions
|
|
View settingsButton = findViewById(R.id.settings_button);
|
|
boolean hasSettings = hasSettings();
|
|
if (hasSettings) {
|
|
settingsButton.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View view) {
|
|
if (!mWorkspace.isSwitchingState()) {
|
|
onClickSettingsButton(view);
|
|
}
|
|
}
|
|
});
|
|
settingsButton.setOnLongClickListener(performClickOnLongClick);
|
|
settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
|
|
} else {
|
|
settingsButton.setVisibility(View.GONE);
|
|
}
|
|
|
|
mOverviewPanel.setAlpha(0f);
|
|
}
|
|
|
|
/**
|
|
* Sets the all apps button. This method is called from {@link Hotseat}.
|
|
* TODO: Get rid of this.
|
|
*/
|
|
public void setAllAppsButton(View allAppsButton) {
|
|
mAllAppsButton = allAppsButton;
|
|
}
|
|
|
|
public View getStartViewForAllAppsRevealAnimation() {
|
|
return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
|
|
}
|
|
|
|
public View getWidgetsButton() {
|
|
return mWidgetsButton;
|
|
}
|
|
|
|
/**
|
|
* Creates a view representing a shortcut.
|
|
*
|
|
* @param info The data structure describing the shortcut.
|
|
*/
|
|
View createShortcut(ShortcutInfo info) {
|
|
return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
|
|
}
|
|
|
|
/**
|
|
* Creates a view representing a shortcut inflated from the specified resource.
|
|
*
|
|
* @param parent The group the shortcut belongs to.
|
|
* @param info The data structure describing the shortcut.
|
|
*
|
|
* @return A View inflated from layoutResId.
|
|
*/
|
|
public View createShortcut(ViewGroup parent, ShortcutInfo info) {
|
|
BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon,
|
|
parent, false);
|
|
favorite.applyFromShortcutInfo(info, mIconCache);
|
|
favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
|
|
favorite.setOnClickListener(this);
|
|
favorite.setOnFocusChangeListener(mFocusHandler);
|
|
return favorite;
|
|
}
|
|
|
|
/**
|
|
* Add a shortcut to the workspace.
|
|
*
|
|
* @param data The intent describing the shortcut.
|
|
*/
|
|
private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
|
|
int cellY) {
|
|
int[] cellXY = mTmpAddItemCellCoordinates;
|
|
CellLayout layout = getCellLayout(container, screenId);
|
|
|
|
ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
|
|
if (info == null) {
|
|
return;
|
|
}
|
|
final View view = createShortcut(info);
|
|
|
|
boolean foundCellSpan = false;
|
|
// 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;
|
|
foundCellSpan = true;
|
|
|
|
// If appropriate, either create a folder or add to an existing folder
|
|
if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
|
|
true, null,null)) {
|
|
return;
|
|
}
|
|
DragObject dragObject = new DragObject();
|
|
dragObject.dragInfo = info;
|
|
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
|
|
true)) {
|
|
return;
|
|
}
|
|
} else {
|
|
foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
|
|
}
|
|
|
|
if (!foundCellSpan) {
|
|
showOutOfSpaceMessage(isHotseatLayout(layout));
|
|
return;
|
|
}
|
|
|
|
LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
|
|
|
|
if (!mRestoring) {
|
|
mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
|
|
isWorkspaceLocked());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a widget to the workspace.
|
|
*
|
|
* @param appWidgetId The app widget id
|
|
*/
|
|
@Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
|
|
AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
|
|
|
|
ItemInfo info = mPendingAddInfo;
|
|
if (appWidgetInfo == null) {
|
|
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
|
|
}
|
|
|
|
if (appWidgetInfo.isCustomWidget) {
|
|
appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
|
|
}
|
|
|
|
LauncherAppWidgetInfo launcherInfo;
|
|
launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
|
|
launcherInfo.spanX = info.spanX;
|
|
launcherInfo.spanY = info.spanY;
|
|
launcherInfo.minSpanX = info.minSpanX;
|
|
launcherInfo.minSpanY = info.minSpanY;
|
|
launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
|
|
|
|
LauncherModel.addItemToDatabase(this, launcherInfo,
|
|
container, screenId, info.cellX, info.cellY);
|
|
|
|
if (!mRestoring) {
|
|
if (hostView == null) {
|
|
// Perform actual inflation because we're live
|
|
hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
|
|
}
|
|
hostView.setVisibility(View.VISIBLE);
|
|
addAppWidgetToWorkspace(hostView, launcherInfo, appWidgetInfo, isWorkspaceLocked());
|
|
}
|
|
resetAddInfo();
|
|
}
|
|
|
|
private void addAppWidgetToWorkspace(
|
|
AppWidgetHostView hostView, LauncherAppWidgetInfo item,
|
|
LauncherAppWidgetProviderInfo appWidgetInfo, boolean insert) {
|
|
hostView.setTag(item);
|
|
item.onBindAppWidget(this, hostView);
|
|
|
|
hostView.setFocusable(true);
|
|
hostView.setOnFocusChangeListener(mFocusHandler);
|
|
|
|
mWorkspace.addInScreen(hostView, item.container, item.screenId,
|
|
item.cellX, item.cellY, item.spanX, item.spanY, insert);
|
|
|
|
if (!item.isCustomWidget()) {
|
|
addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
|
|
}
|
|
}
|
|
|
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
final String action = intent.getAction();
|
|
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
|
|
mUserPresent = false;
|
|
mDragLayer.clearAllResizeFrames();
|
|
updateAutoAdvanceState();
|
|
|
|
// Reset AllApps to its initial state only if we are not in the middle of
|
|
// processing a multi-step drop
|
|
if (mAppsView != null && mWidgetsView != null &&
|
|
mPendingAddInfo.container == ItemInfo.NO_ID) {
|
|
if (!showWorkspace(false)) {
|
|
// If we are already on the workspace, then manually reset all apps
|
|
mAppsView.reset();
|
|
}
|
|
}
|
|
mIsResumeFromActionScreenOff = true;
|
|
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
|
|
mUserPresent = true;
|
|
updateAutoAdvanceState();
|
|
}
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void onAttachedToWindow() {
|
|
super.onAttachedToWindow();
|
|
|
|
// Listen for broadcasts related to user-presence
|
|
final IntentFilter filter = new IntentFilter();
|
|
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
|
filter.addAction(Intent.ACTION_USER_PRESENT);
|
|
registerReceiver(mReceiver, filter);
|
|
FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
|
|
mAttached = true;
|
|
mVisible = true;
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onAttachedToWindow();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
mVisible = false;
|
|
|
|
if (mAttached) {
|
|
unregisterReceiver(mReceiver);
|
|
mAttached = false;
|
|
}
|
|
updateAutoAdvanceState();
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onDetachedFromWindow();
|
|
}
|
|
}
|
|
|
|
public void onWindowVisibilityChanged(int visibility) {
|
|
mVisible = visibility == View.VISIBLE;
|
|
updateAutoAdvanceState();
|
|
// The following code used to be in onResume, but it turns out onResume is called when
|
|
// you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
|
|
// is a more appropriate event to handle
|
|
if (mVisible) {
|
|
if (!mWorkspaceLoading) {
|
|
final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
|
|
// We want to let Launcher draw itself at least once before we force it to build
|
|
// layers on all the workspace pages, so that transitioning to Launcher from other
|
|
// apps is nice and speedy.
|
|
observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
|
|
private boolean mStarted = false;
|
|
public void onDraw() {
|
|
if (mStarted) return;
|
|
mStarted = true;
|
|
// We delay the layer building a bit in order to give
|
|
// other message processing a time to run. In particular
|
|
// this avoids a delay in hiding the IME if it was
|
|
// currently shown, because doing that may involve
|
|
// some communication back with the app.
|
|
mWorkspace.postDelayed(mBuildLayersRunnable, 500);
|
|
final ViewTreeObserver.OnDrawListener listener = this;
|
|
mWorkspace.post(new Runnable() {
|
|
public void run() {
|
|
if (mWorkspace != null &&
|
|
mWorkspace.getViewTreeObserver() != null) {
|
|
mWorkspace.getViewTreeObserver().
|
|
removeOnDrawListener(listener);
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
clearTypedText();
|
|
}
|
|
}
|
|
|
|
@Thunk void sendAdvanceMessage(long delay) {
|
|
mHandler.removeMessages(ADVANCE_MSG);
|
|
Message msg = mHandler.obtainMessage(ADVANCE_MSG);
|
|
mHandler.sendMessageDelayed(msg, delay);
|
|
mAutoAdvanceSentTime = System.currentTimeMillis();
|
|
}
|
|
|
|
@Thunk void updateAutoAdvanceState() {
|
|
boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
|
|
if (autoAdvanceRunning != mAutoAdvanceRunning) {
|
|
mAutoAdvanceRunning = autoAdvanceRunning;
|
|
if (autoAdvanceRunning) {
|
|
long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
|
|
sendAdvanceMessage(delay);
|
|
} else {
|
|
if (!mWidgetsToAdvance.isEmpty()) {
|
|
mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
|
|
(System.currentTimeMillis() - mAutoAdvanceSentTime));
|
|
}
|
|
mHandler.removeMessages(ADVANCE_MSG);
|
|
mHandler.removeMessages(0); // Remove messages sent using postDelayed()
|
|
}
|
|
}
|
|
}
|
|
|
|
@Thunk final Handler mHandler = new Handler(new Handler.Callback() {
|
|
|
|
@Override
|
|
public boolean handleMessage(Message msg) {
|
|
if (msg.what == ADVANCE_MSG) {
|
|
int i = 0;
|
|
for (View key: mWidgetsToAdvance.keySet()) {
|
|
final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
|
|
final int delay = ADVANCE_STAGGER * i;
|
|
if (v instanceof Advanceable) {
|
|
mHandler.postDelayed(new Runnable() {
|
|
public void run() {
|
|
((Advanceable) v).advance();
|
|
}
|
|
}, delay);
|
|
}
|
|
i++;
|
|
}
|
|
sendAdvanceMessage(ADVANCE_INTERVAL);
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
|
|
private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
|
|
if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
|
|
View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
|
|
if (v instanceof Advanceable) {
|
|
mWidgetsToAdvance.put(hostView, appWidgetInfo);
|
|
((Advanceable) v).fyiWillBeAdvancedByHostKThx();
|
|
updateAutoAdvanceState();
|
|
}
|
|
}
|
|
|
|
private void removeWidgetToAutoAdvance(View hostView) {
|
|
if (mWidgetsToAdvance.containsKey(hostView)) {
|
|
mWidgetsToAdvance.remove(hostView);
|
|
updateAutoAdvanceState();
|
|
}
|
|
}
|
|
|
|
public void showOutOfSpaceMessage(boolean isHotseatLayout) {
|
|
int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
|
|
Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
public DragLayer getDragLayer() {
|
|
return mDragLayer;
|
|
}
|
|
|
|
public AllAppsContainerView getAppsView() {
|
|
return mAppsView;
|
|
}
|
|
|
|
public WidgetsContainerView getWidgetsView() {
|
|
return mWidgetsView;
|
|
}
|
|
|
|
public Workspace getWorkspace() {
|
|
return mWorkspace;
|
|
}
|
|
|
|
public View getQsbContainer() {
|
|
return mQsbContainer;
|
|
}
|
|
|
|
public Hotseat getHotseat() {
|
|
return mHotseat;
|
|
}
|
|
|
|
public ViewGroup getOverviewPanel() {
|
|
return mOverviewPanel;
|
|
}
|
|
|
|
public DropTargetBar getDropTargetBar() {
|
|
return mDropTargetBar;
|
|
}
|
|
|
|
public LauncherAppWidgetHost getAppWidgetHost() {
|
|
return mAppWidgetHost;
|
|
}
|
|
|
|
public LauncherModel getModel() {
|
|
return mModel;
|
|
}
|
|
|
|
public SharedPreferences getSharedPrefs() {
|
|
return mSharedPrefs;
|
|
}
|
|
|
|
public DeviceProfile getDeviceProfile() {
|
|
return mDeviceProfile;
|
|
}
|
|
|
|
public void closeSystemDialogs() {
|
|
getWindow().closeAllPanels();
|
|
|
|
// Whatever we were doing is hereby canceled.
|
|
setWaitingForResult(false);
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
long startTime = 0;
|
|
if (DEBUG_RESUME_TIME) {
|
|
startTime = System.currentTimeMillis();
|
|
}
|
|
super.onNewIntent(intent);
|
|
|
|
// Close the menu
|
|
Folder openFolder = mWorkspace.getOpenFolder();
|
|
boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
|
|
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
|
|
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
|
|
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
|
|
if (isActionMain) {
|
|
// also will cancel mWaitingForResult.
|
|
closeSystemDialogs();
|
|
|
|
if (mWorkspace == null) {
|
|
// Can be cases where mWorkspace is null, this prevents a NPE
|
|
return;
|
|
}
|
|
// In all these cases, only animate if we're already on home
|
|
mWorkspace.exitWidgetResizeMode();
|
|
|
|
closeFolder(alreadyOnHome);
|
|
closeShortcutsContainer(alreadyOnHome);
|
|
exitSpringLoadedDragMode();
|
|
|
|
// If we are already on home, then just animate back to the workspace,
|
|
// otherwise, just wait until onResume to set the state back to Workspace
|
|
if (alreadyOnHome) {
|
|
showWorkspace(true);
|
|
} else {
|
|
mOnResumeState = State.WORKSPACE;
|
|
}
|
|
|
|
final View v = getWindow().peekDecorView();
|
|
if (v != null && v.getWindowToken() != null) {
|
|
InputMethodManager imm = (InputMethodManager) getSystemService(
|
|
INPUT_METHOD_SERVICE);
|
|
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
|
}
|
|
|
|
// Reset the apps view
|
|
if (!alreadyOnHome && mAppsView != null) {
|
|
mAppsView.scrollToTop();
|
|
}
|
|
|
|
// Reset the widgets view
|
|
if (!alreadyOnHome && mWidgetsView != null) {
|
|
mWidgetsView.scrollToTop();
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onHomeIntent();
|
|
}
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onNewIntent(intent);
|
|
}
|
|
|
|
// Defer moving to the default screen until after we callback to the LauncherCallbacks
|
|
// as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
|
|
// animation.
|
|
if (isActionMain) {
|
|
boolean moveToDefaultScreen = mLauncherCallbacks != null ?
|
|
mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
|
|
if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
|
|
openFolder == null && moveToDefaultScreen) {
|
|
|
|
// We use this flag to suppress noisy callbacks above custom content state
|
|
// from onResume.
|
|
mMoveToDefaultScreenFromNewIntent = true;
|
|
mWorkspace.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (mWorkspace != null) {
|
|
mWorkspace.moveToDefaultScreen(true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (DEBUG_RESUME_TIME) {
|
|
Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRestoreInstanceState(Bundle state) {
|
|
super.onRestoreInstanceState(state);
|
|
for (int page: mSynchronouslyBoundPages) {
|
|
mWorkspace.restoreInstanceStateForChild(page);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
if (mWorkspace.getChildCount() > 0) {
|
|
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
|
|
mWorkspace.getCurrentPageOffsetFromCustomContent());
|
|
|
|
}
|
|
super.onSaveInstanceState(outState);
|
|
|
|
outState.putInt(RUNTIME_STATE, mState.ordinal());
|
|
// We close any open folder since it will not be re-opened, and we need to make sure
|
|
// this state is reflected.
|
|
// TODO: Move folderInfo.isOpened out of the model and make it a UI state.
|
|
closeFolder(false);
|
|
closeShortcutsContainer(false);
|
|
|
|
if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
|
|
mWaitingForResult) {
|
|
ContentValues itemValues = new ContentValues();
|
|
mPendingAddInfo.writeToValues(itemValues);
|
|
outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues);
|
|
outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
|
|
outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onSaveInstanceState(outState);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
|
|
// Remove all pending runnables
|
|
mHandler.removeMessages(ADVANCE_MSG);
|
|
mHandler.removeMessages(0);
|
|
mWorkspace.removeCallbacks(mBuildLayersRunnable);
|
|
mWorkspace.removeFolderListeners();
|
|
|
|
// Stop callbacks from LauncherModel
|
|
// 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();
|
|
LauncherAppState.getInstance().setLauncher(null);
|
|
}
|
|
|
|
if (mRotationPrefChangeHandler != null) {
|
|
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
|
|
}
|
|
|
|
try {
|
|
mAppWidgetHost.stopListening();
|
|
} catch (NullPointerException ex) {
|
|
Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
|
|
}
|
|
mAppWidgetHost = null;
|
|
|
|
mWidgetsToAdvance.clear();
|
|
|
|
TextKeyListener.getInstance().release();
|
|
|
|
((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
|
|
.removeAccessibilityStateChangeListener(this);
|
|
|
|
unregisterReceiver(mUiBroadcastReceiver);
|
|
|
|
LauncherAnimUtils.onDestroyActivity();
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onDestroy();
|
|
}
|
|
}
|
|
|
|
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
|
|
return mAccessibilityDelegate;
|
|
}
|
|
|
|
public DragController getDragController() {
|
|
return mDragController;
|
|
}
|
|
|
|
@Override
|
|
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
|
onStartForResult(requestCode);
|
|
super.startActivityForResult(intent, requestCode, options);
|
|
}
|
|
|
|
@Override
|
|
public void startIntentSenderForResult (IntentSender intent, int requestCode,
|
|
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
|
|
onStartForResult(requestCode);
|
|
try {
|
|
super.startIntentSenderForResult(intent, requestCode,
|
|
fillInIntent, flagsMask, flagsValues, extraFlags, options);
|
|
} catch (IntentSender.SendIntentException e) {
|
|
throw new ActivityNotFoundException();
|
|
}
|
|
}
|
|
|
|
private void onStartForResult(int requestCode) {
|
|
if (requestCode >= 0) {
|
|
setWaitingForResult(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates that we want global search for this activity by setting the globalSearch
|
|
* argument for {@link #startSearch} to true.
|
|
*/
|
|
@Override
|
|
public void startSearch(String initialQuery, boolean selectInitialQuery,
|
|
Bundle appSearchData, boolean globalSearch) {
|
|
|
|
if (initialQuery == null) {
|
|
// Use any text typed in the launcher as the initial query
|
|
initialQuery = getTypedText();
|
|
}
|
|
if (appSearchData == null) {
|
|
appSearchData = new Bundle();
|
|
appSearchData.putString("source", "launcher-search");
|
|
}
|
|
|
|
// TODO send proper bounds.
|
|
Rect sourceBounds = null;
|
|
|
|
boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
|
|
appSearchData, sourceBounds);
|
|
if (clearTextImmediately) {
|
|
clearTypedText();
|
|
}
|
|
|
|
// We need to show the workspace after starting the search
|
|
showWorkspace(true);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
|
|
return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
|
|
sourceBounds);
|
|
}
|
|
|
|
startGlobalSearch(initialQuery, selectInitialQuery,
|
|
appSearchData, sourceBounds);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Starts the global search activity. This code is a copied from SearchManager
|
|
*/
|
|
private void startGlobalSearch(String initialQuery,
|
|
boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
|
|
final SearchManager searchManager =
|
|
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
|
ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
|
|
if (globalSearchActivity == null) {
|
|
Log.w(TAG, "No global search activity found.");
|
|
return;
|
|
}
|
|
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
intent.setComponent(globalSearchActivity);
|
|
// Make sure that we have a Bundle to put source in
|
|
if (appSearchData == null) {
|
|
appSearchData = new Bundle();
|
|
} else {
|
|
appSearchData = new Bundle(appSearchData);
|
|
}
|
|
// Set source to package name of app that starts global search if not set already.
|
|
if (!appSearchData.containsKey("source")) {
|
|
appSearchData.putString("source", getPackageName());
|
|
}
|
|
intent.putExtra(SearchManager.APP_DATA, appSearchData);
|
|
if (!TextUtils.isEmpty(initialQuery)) {
|
|
intent.putExtra(SearchManager.QUERY, initialQuery);
|
|
}
|
|
if (selectInitialQuery) {
|
|
intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
|
|
}
|
|
intent.setSourceBounds(sourceBounds);
|
|
try {
|
|
startActivity(intent);
|
|
} catch (ActivityNotFoundException ex) {
|
|
Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
|
|
}
|
|
}
|
|
|
|
public boolean isOnCustomContent() {
|
|
return mWorkspace.isOnOrMovingToCustomContent();
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
super.onPrepareOptionsMenu(menu);
|
|
if (mLauncherCallbacks != null) {
|
|
return mLauncherCallbacks.onPrepareOptionsMenu(menu);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onSearchRequested() {
|
|
startSearch(null, false, null, true);
|
|
// Use a custom animation for launching search
|
|
return true;
|
|
}
|
|
|
|
public boolean isWorkspaceLocked() {
|
|
return mWorkspaceLoading || mWaitingForResult;
|
|
}
|
|
|
|
public boolean isWorkspaceLoading() {
|
|
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() {
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onWorkspaceLockedChanged();
|
|
}
|
|
}
|
|
|
|
private void resetAddInfo() {
|
|
mPendingAddInfo.container = ItemInfo.NO_ID;
|
|
mPendingAddInfo.screenId = -1;
|
|
mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
|
|
mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
|
|
mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
|
|
}
|
|
|
|
void addAppWidgetFromDropImpl(final int appWidgetId, final ItemInfo info, final
|
|
AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
|
|
if (LOGD) {
|
|
Log.d(TAG, "Adding widget from drop");
|
|
}
|
|
addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
|
|
}
|
|
|
|
void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
|
|
final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
|
|
int delay) {
|
|
if (appWidgetInfo.configure != null) {
|
|
mPendingAddWidgetInfo = appWidgetInfo;
|
|
mPendingAddWidgetId = appWidgetId;
|
|
|
|
// Launch over to configure widget, if needed
|
|
mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
|
|
mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
|
|
|
|
} else {
|
|
// Otherwise just add it
|
|
Runnable onComplete = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Exit spring loaded mode if necessary after adding the widget
|
|
exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
|
|
null);
|
|
}
|
|
};
|
|
completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
|
|
appWidgetInfo);
|
|
mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
|
|
}
|
|
}
|
|
|
|
protected void moveToCustomContentScreen(boolean animate) {
|
|
// Close any folders that may be open.
|
|
closeFolder();
|
|
mWorkspace.moveToCustomContentScreen(animate);
|
|
}
|
|
|
|
public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
|
|
int[] cell, int spanX, int spanY) {
|
|
switch (info.itemType) {
|
|
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
|
|
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
|
|
int span[] = new int[2];
|
|
span[0] = spanX;
|
|
span[1] = spanY;
|
|
addAppWidgetFromDrop((PendingAddWidgetInfo) info,
|
|
container, screenId, cell, span);
|
|
break;
|
|
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
|
|
processShortcutFromDrop(info.componentName, container, screenId, cell);
|
|
break;
|
|
default:
|
|
throw new IllegalStateException("Unknown item type: " + info.itemType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process a shortcut drop.
|
|
*
|
|
* @param componentName The name of the component
|
|
* @param screenId The ID of the screen where it should be added
|
|
* @param cell The cell it should be added to, optional
|
|
*/
|
|
private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
|
|
int[] cell) {
|
|
resetAddInfo();
|
|
mPendingAddInfo.container = container;
|
|
mPendingAddInfo.screenId = screenId;
|
|
|
|
if (cell != null) {
|
|
mPendingAddInfo.cellX = cell[0];
|
|
mPendingAddInfo.cellY = cell[1];
|
|
}
|
|
|
|
Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
|
|
createShortcutIntent.setComponent(componentName);
|
|
Utilities.startActivityForResultSafely(this, createShortcutIntent, REQUEST_CREATE_SHORTCUT);
|
|
}
|
|
|
|
/**
|
|
* Process a widget drop.
|
|
*
|
|
* @param info The PendingAppWidgetInfo of the widget being added.
|
|
* @param screenId The ID of the screen where it should be added
|
|
* @param cell The cell it should be added to, optional
|
|
*/
|
|
private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
|
|
int[] cell, int[] span) {
|
|
resetAddInfo();
|
|
mPendingAddInfo.container = info.container = container;
|
|
mPendingAddInfo.screenId = info.screenId = screenId;
|
|
mPendingAddInfo.minSpanX = info.minSpanX;
|
|
mPendingAddInfo.minSpanY = info.minSpanY;
|
|
|
|
if (cell != null) {
|
|
mPendingAddInfo.cellX = cell[0];
|
|
mPendingAddInfo.cellY = cell[1];
|
|
}
|
|
if (span != null) {
|
|
mPendingAddInfo.spanX = span[0];
|
|
mPendingAddInfo.spanY = span[1];
|
|
}
|
|
|
|
AppWidgetHostView hostView = info.boundWidget;
|
|
int appWidgetId;
|
|
if (hostView != null) {
|
|
// In the case where we've prebound the widget, we remove it from the DragLayer
|
|
if (LOGD) {
|
|
Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null");
|
|
}
|
|
getDragLayer().removeView(hostView);
|
|
|
|
appWidgetId = hostView.getAppWidgetId();
|
|
addAppWidgetFromDropImpl(appWidgetId, info, hostView, info.info);
|
|
|
|
// Clear the boundWidget so that it doesn't get destroyed.
|
|
info.boundWidget = null;
|
|
} else {
|
|
// In this case, we either need to start an activity to get permission to bind
|
|
// the widget, or we need to start an activity to configure the widget, or both.
|
|
appWidgetId = getAppWidgetHost().allocateAppWidgetId();
|
|
Bundle options = info.bindOptions;
|
|
|
|
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
|
|
appWidgetId, info.info, options);
|
|
if (success) {
|
|
addAppWidgetFromDropImpl(appWidgetId, info, null, info.info);
|
|
} else {
|
|
mPendingAddWidgetInfo = info.info;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
|
|
int cellY) {
|
|
final FolderInfo folderInfo = new FolderInfo();
|
|
folderInfo.title = getText(R.string.folder_name);
|
|
|
|
// Update the model
|
|
LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
|
|
cellX, cellY);
|
|
|
|
// Create the view
|
|
FolderIcon newFolder =
|
|
FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
|
|
mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
|
|
isWorkspaceLocked());
|
|
// Force measure the new folder icon
|
|
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
|
|
parent.getShortcutsAndWidgets().measureChild(newFolder);
|
|
return newFolder;
|
|
}
|
|
|
|
/**
|
|
* Unbinds the view for the specified item, and removes the item and all its children.
|
|
*
|
|
* @param v the view being removed.
|
|
* @param itemInfo the {@link ItemInfo} for this view.
|
|
* @param deleteFromDb whether or not to delete this item from the db.
|
|
*/
|
|
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
|
|
if (itemInfo instanceof ShortcutInfo) {
|
|
// Remove the shortcut from the folder before removing it from launcher
|
|
View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
|
|
if (folderIcon instanceof FolderIcon) {
|
|
((FolderInfo) folderIcon.getTag()).remove((ShortcutInfo) itemInfo, true);
|
|
} else {
|
|
mWorkspace.removeWorkspaceItem(v);
|
|
}
|
|
if (deleteFromDb) {
|
|
LauncherModel.deleteItemFromDatabase(this, itemInfo);
|
|
}
|
|
} else if (itemInfo instanceof FolderInfo) {
|
|
final FolderInfo folderInfo = (FolderInfo) itemInfo;
|
|
if (v instanceof FolderIcon) {
|
|
((FolderIcon) v).removeListeners();
|
|
}
|
|
mWorkspace.removeWorkspaceItem(v);
|
|
if (deleteFromDb) {
|
|
LauncherModel.deleteFolderAndContentsFromDatabase(this, folderInfo);
|
|
}
|
|
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
|
|
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
|
|
mWorkspace.removeWorkspaceItem(v);
|
|
removeWidgetToAutoAdvance(v);
|
|
if (deleteFromDb) {
|
|
deleteWidgetInfo(widgetInfo);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Deletes the widget info and the widget id.
|
|
*/
|
|
private void deleteWidgetInfo(final LauncherAppWidgetInfo widgetInfo) {
|
|
final LauncherAppWidgetHost appWidgetHost = getAppWidgetHost();
|
|
if (appWidgetHost != null && !widgetInfo.isCustomWidget() && widgetInfo.isWidgetIdAllocated()) {
|
|
// Deleting an app widget ID is a void call but writes to disk before returning
|
|
// to the caller...
|
|
new AsyncTask<Void, Void, Void>() {
|
|
public Void doInBackground(Void ... args) {
|
|
appWidgetHost.deleteAppWidgetId(widgetInfo.appWidgetId);
|
|
return null;
|
|
}
|
|
}.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
|
|
}
|
|
LauncherModel.deleteItemFromDatabase(this, widgetInfo);
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
switch (event.getKeyCode()) {
|
|
case KeyEvent.KEYCODE_HOME:
|
|
return true;
|
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
|
|
dumpState();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
|
switch (event.getKeyCode()) {
|
|
case KeyEvent.KEYCODE_HOME:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return super.dispatchKeyEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
|
|
return;
|
|
}
|
|
|
|
if (mDragController.isDragging()) {
|
|
mDragController.cancelDrag();
|
|
return;
|
|
}
|
|
|
|
if (getOpenShortcutsContainer() != null) {
|
|
closeShortcutsContainer();
|
|
} else if (isAppsViewVisible()) {
|
|
showWorkspace(true);
|
|
} else if (isWidgetsViewVisible()) {
|
|
showOverviewMode(true);
|
|
} else if (mWorkspace.isInOverviewMode()) {
|
|
showWorkspace(true);
|
|
} else if (mWorkspace.getOpenFolder() != null) {
|
|
Folder openFolder = mWorkspace.getOpenFolder();
|
|
if (openFolder.isEditingName()) {
|
|
openFolder.dismissEditingName();
|
|
} else {
|
|
closeFolder();
|
|
}
|
|
} else {
|
|
mWorkspace.exitWidgetResizeMode();
|
|
|
|
// Back button is a no-op here, but give at least some feedback for the button press
|
|
mWorkspace.showOutlinesTemporarily();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches the intent referred by the clicked shortcut.
|
|
*
|
|
* @param v The view representing the clicked shortcut.
|
|
*/
|
|
public void onClick(View v) {
|
|
// Make sure that rogue clicks don't get through while allapps is launching, or after the
|
|
// view has detached (it's possible for this to happen if the view is removed mid touch).
|
|
if (v.getWindowToken() == null) {
|
|
return;
|
|
}
|
|
|
|
if (!mWorkspace.isFinishedSwitchingState()) {
|
|
return;
|
|
}
|
|
|
|
if (v instanceof Workspace) {
|
|
if (mWorkspace.isInOverviewMode()) {
|
|
showWorkspace(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (v instanceof CellLayout) {
|
|
if (mWorkspace.isInOverviewMode()) {
|
|
mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v));
|
|
showWorkspace(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Object tag = v.getTag();
|
|
if (tag instanceof ShortcutInfo) {
|
|
onClickAppShortcut(v);
|
|
} else if (tag instanceof FolderInfo) {
|
|
if (v instanceof FolderIcon) {
|
|
onClickFolderIcon(v);
|
|
}
|
|
} else if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
|
|
(v == mAllAppsButton && mAllAppsButton != null)) {
|
|
onClickAllAppsButton(v);
|
|
} else if (tag instanceof AppInfo) {
|
|
startAppShortcutOrInfoActivity(v);
|
|
} else if (tag instanceof LauncherAppWidgetInfo) {
|
|
if (v instanceof PendingAppWidgetHostView) {
|
|
onClickPendingWidget((PendingAppWidgetHostView) v);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
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) {
|
|
if (mIsSafeModeEnabled) {
|
|
Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
|
|
return;
|
|
}
|
|
|
|
final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
|
|
if (v.isReadyForClickSetup()) {
|
|
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
|
|
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
|
|
// This should not happen, as we make sure that an Id is allocated during bind.
|
|
return;
|
|
}
|
|
LauncherAppWidgetProviderInfo appWidgetInfo =
|
|
mAppWidgetManager.findProvider(info.providerName, info.user);
|
|
if (appWidgetInfo != null) {
|
|
mPendingAddWidgetId = info.appWidgetId;
|
|
mPendingAddInfo.copyFrom(info);
|
|
mPendingAddWidgetInfo = appWidgetInfo;
|
|
|
|
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
|
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mPendingAddWidgetId);
|
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);
|
|
mAppWidgetManager.getUser(mPendingAddWidgetInfo)
|
|
.addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
|
|
startActivityForResult(intent, REQUEST_BIND_PENDING_APPWIDGET);
|
|
}
|
|
} else {
|
|
LauncherAppWidgetProviderInfo appWidgetInfo =
|
|
mAppWidgetManager.getLauncherAppWidgetInfo(info.appWidgetId);
|
|
if (appWidgetInfo != null) {
|
|
startRestoredWidgetReconfigActivity(appWidgetInfo, info);
|
|
}
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
private void startRestoredWidgetReconfigActivity(
|
|
LauncherAppWidgetProviderInfo provider, LauncherAppWidgetInfo info) {
|
|
mPendingAddWidgetInfo = provider;
|
|
mPendingAddInfo.copyFrom(info);
|
|
mPendingAddWidgetId = info.appWidgetId;
|
|
mAppWidgetManager.startConfigActivity(provider,
|
|
info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
|
|
}
|
|
|
|
/**
|
|
* Event handler for the "grid" button that appears on the home screen, which
|
|
* enters all apps mode.
|
|
*
|
|
* @param v The view that was clicked.
|
|
*/
|
|
protected void onClickAllAppsButton(View v) {
|
|
if (LOGD) Log.d(TAG, "onClickAllAppsButton");
|
|
if (!isAppsViewVisible()) {
|
|
getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.TAP,
|
|
LauncherLogProto.ALL_APPS_BUTTON);
|
|
showAppsView(true /* animated */, true /* updatePredictedApps */,
|
|
false /* focusSearchBar */);
|
|
}
|
|
}
|
|
|
|
protected void onLongClickAllAppsButton(View v) {
|
|
if (LOGD) Log.d(TAG, "onLongClickAllAppsButton");
|
|
if (!isAppsViewVisible()) {
|
|
getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.LONGPRESS,
|
|
LauncherLogProto.ALL_APPS_BUTTON);
|
|
showAppsView(true /* animated */,
|
|
true /* updatePredictedApps */, true /* focusSearchBar */);
|
|
}
|
|
}
|
|
|
|
private void showBrokenAppInstallDialog(final String packageName,
|
|
DialogInterface.OnClickListener onSearchClickListener) {
|
|
new AlertDialog.Builder(this)
|
|
.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;
|
|
|
|
if (shortcut.isDisabled != 0) {
|
|
if ((shortcut.isDisabled &
|
|
~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
|
|
~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
|
|
// If the app is only disabled because of the above flags, launch activity anyway.
|
|
// Framework will tell the user why the app is suspended.
|
|
} else {
|
|
if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
|
|
// Use a message specific to this shortcut, if it has one.
|
|
Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
|
|
return;
|
|
}
|
|
// Otherwise just use a generic error message.
|
|
int error = R.string.activity_not_available;
|
|
if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
|
|
error = R.string.safemode_shortcut_error;
|
|
} else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
|
|
(shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
|
|
error = R.string.shortcut_not_available;
|
|
}
|
|
Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
|
|
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) {
|
|
ItemInfo item = (ItemInfo) v.getTag();
|
|
Intent intent = item.getIntent();
|
|
if (intent == null) {
|
|
throw new IllegalArgumentException("Input must have a valid intent");
|
|
}
|
|
boolean success = startActivitySafely(v, intent, item);
|
|
getUserEventDispatcher().logAppLaunch(v, intent);
|
|
|
|
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;
|
|
if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) {
|
|
// Open the requested folder
|
|
openFolder(folderIcon);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event handler for the (Add) Widgets button that appears after a long press
|
|
* on the home screen.
|
|
*/
|
|
public void onClickAddWidgetButton(View view) {
|
|
if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
|
|
if (mIsSafeModeEnabled) {
|
|
Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
|
|
} else {
|
|
showWidgetsView(true /* animated */, true /* resetPageToZero */);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event handler for the wallpaper picker button that appears after a long press
|
|
* on the home screen.
|
|
*/
|
|
public void onClickWallpaperPicker(View v) {
|
|
if (!Utilities.isWallapaperAllowed(this)) {
|
|
Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
|
|
return;
|
|
}
|
|
|
|
String pickerPackage = getString(R.string.wallpaper_picker_package);
|
|
if (TextUtils.isEmpty(pickerPackage)) {
|
|
pickerPackage = PackageManagerHelper.getWallpaperPickerPackage(getPackageManager());
|
|
}
|
|
|
|
int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen());
|
|
float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
|
|
|
|
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
|
|
.setPackage(pickerPackage)
|
|
.putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset);
|
|
intent.setSourceBounds(getViewBounds(v));
|
|
startActivityForResult(intent, REQUEST_PICK_WALLPAPER, getActivityLaunchOptions(v));
|
|
}
|
|
|
|
/**
|
|
* Event handler for a click on the settings button that appears after a long press
|
|
* on the home screen.
|
|
*/
|
|
public void onClickSettingsButton(View v) {
|
|
if (LOGD) Log.d(TAG, "onClickSettingsButton");
|
|
Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
|
|
.setPackage(getPackageName());
|
|
intent.setSourceBounds(getViewBounds(v));
|
|
startActivity(intent, getActivityLaunchOptions(v));
|
|
}
|
|
|
|
public View.OnTouchListener getHapticFeedbackTouchListener() {
|
|
if (mHapticFeedbackTouchListener == null) {
|
|
mHapticFeedbackTouchListener = new View.OnTouchListener() {
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
|
|
v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
return mHapticFeedbackTouchListener;
|
|
}
|
|
|
|
@Override
|
|
public void onAccessibilityStateChanged(boolean enabled) {
|
|
mDragLayer.onAccessibilityStateChanged(enabled);
|
|
}
|
|
|
|
public void onDragStarted() {
|
|
if (isOnCustomContent()) {
|
|
// Custom content screen doesn't participate in drag and drop. If on custom
|
|
// content screen, move to default.
|
|
moveWorkspaceToDefaultScreen();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the user stops interacting with the launcher.
|
|
* This implies that the user is now on the homescreen and is not doing housekeeping.
|
|
*/
|
|
protected void onInteractionEnd() {
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onInteractionEnd();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the user starts interacting with the launcher.
|
|
* The possible interactions are:
|
|
* - open all apps
|
|
* - reorder an app shortcut, or a widget
|
|
* - open the overview mode.
|
|
* This is a good time to stop doing things that only make sense
|
|
* when the user is on the homescreen and not doing housekeeping.
|
|
*/
|
|
protected void onInteractionBegin() {
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onInteractionBegin();
|
|
}
|
|
}
|
|
|
|
/** Updates the interaction state. */
|
|
public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
|
|
// Only update the interacting state if we are transitioning to/from a view with an
|
|
// overlay
|
|
boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL;
|
|
boolean toStateWithOverlay = toState != Workspace.State.NORMAL;
|
|
if (toStateWithOverlay) {
|
|
onInteractionBegin();
|
|
} else if (fromStateWithOverlay) {
|
|
onInteractionEnd();
|
|
}
|
|
}
|
|
|
|
private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
|
|
try {
|
|
StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
|
|
try {
|
|
// Temporarily disable deathPenalty on all default checks. For eg, shortcuts
|
|
// containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure
|
|
// is enabled by default on NYC.
|
|
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll()
|
|
.penaltyLog().build());
|
|
|
|
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
|
String id = ((ShortcutInfo) info).getDeepShortcutId();
|
|
String packageName = intent.getPackage();
|
|
LauncherAppState.getInstance().getShortcutManager().startShortcut(
|
|
packageName, id, intent.getSourceBounds(), optsBundle, info.user);
|
|
} else {
|
|
// Could be launching some bookkeeping activity
|
|
startActivity(intent, optsBundle);
|
|
}
|
|
} finally {
|
|
StrictMode.setVmPolicy(oldPolicy);
|
|
}
|
|
} catch (SecurityException e) {
|
|
// Due to legacy reasons, direct call shortcuts require Launchers to have the
|
|
// corresponding permission. Show the appropriate permission prompt if that
|
|
// is the case.
|
|
if (intent.getComponent() == null
|
|
&& Intent.ACTION_CALL.equals(intent.getAction())
|
|
&& checkSelfPermission(Manifest.permission.CALL_PHONE) !=
|
|
PackageManager.PERMISSION_GRANTED) {
|
|
// TODO: Rename sPendingAddItem to a generic name.
|
|
sPendingAddItem = preparePendingAddArgs(REQUEST_PERMISSION_CALL_PHONE, intent,
|
|
0, info);
|
|
requestPermissions(new String[]{Manifest.permission.CALL_PHONE},
|
|
REQUEST_PERMISSION_CALL_PHONE);
|
|
} else {
|
|
// No idea why this was thrown.
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
private Bundle getActivityLaunchOptions(View v) {
|
|
if (Utilities.ATLEAST_MARSHMALLOW) {
|
|
int left = 0, top = 0;
|
|
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
|
|
if (v instanceof TextView) {
|
|
// Launch from center of icon, not entire view
|
|
Drawable icon = Workspace.getTextViewIcon((TextView) v);
|
|
if (icon != null) {
|
|
Rect bounds = icon.getBounds();
|
|
left = (width - bounds.width()) / 2;
|
|
top = v.getPaddingTop();
|
|
width = bounds.width();
|
|
height = bounds.height();
|
|
}
|
|
}
|
|
return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height).toBundle();
|
|
} else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
|
|
// On L devices, we use the device default slide-up transition.
|
|
// On L MR1 devices, we use a custom version of the slide-up transition which
|
|
// doesn't have the delay present in the device default.
|
|
return ActivityOptions.makeCustomAnimation(
|
|
this, R.anim.task_open_enter, R.anim.no_anim).toBundle();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private Rect getViewBounds(View v) {
|
|
int[] pos = new int[2];
|
|
v.getLocationOnScreen(pos);
|
|
return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
|
|
}
|
|
|
|
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
|
|
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
|
|
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
|
|
return false;
|
|
}
|
|
// 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);
|
|
Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null;
|
|
|
|
UserHandleCompat user = null;
|
|
if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
|
|
long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
|
|
user = UserManagerCompat.getInstance(this).getUserForSerialNumber(serialNumber);
|
|
}
|
|
|
|
// Prepare intent
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
if (v != null) {
|
|
intent.setSourceBounds(getViewBounds(v));
|
|
}
|
|
try {
|
|
if (Utilities.ATLEAST_MARSHMALLOW && item != null
|
|
&& (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
|
|
|| item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
|
|
&& ((ShortcutInfo) item).promisedIntent == null) {
|
|
// Shortcuts need some special checks due to legacy reasons.
|
|
startShortcutIntentSafely(intent, optsBundle, item);
|
|
} else if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
|
|
// Could be launching some bookkeeping activity
|
|
startActivity(intent, optsBundle);
|
|
} else {
|
|
LauncherAppsCompat.getInstance(this).startActivityForProfile(
|
|
intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
|
|
}
|
|
return true;
|
|
} catch (ActivityNotFoundException|SecurityException e) {
|
|
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
|
|
Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
private void copyFolderIconToImage(FolderIcon fi) {
|
|
final int width = fi.getMeasuredWidth();
|
|
final int height = fi.getMeasuredHeight();
|
|
|
|
// Lazy load ImageView, Bitmap and Canvas
|
|
if (mFolderIconImageView == null) {
|
|
mFolderIconImageView = new ImageView(this);
|
|
}
|
|
if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
|
|
mFolderIconBitmap.getHeight() != height) {
|
|
mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
mFolderIconCanvas = new Canvas(mFolderIconBitmap);
|
|
}
|
|
|
|
DragLayer.LayoutParams lp;
|
|
if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
|
|
lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
|
|
} else {
|
|
lp = new DragLayer.LayoutParams(width, height);
|
|
}
|
|
|
|
// The layout from which the folder is being opened may be scaled, adjust the starting
|
|
// view size by this scale factor.
|
|
float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
|
|
lp.customPosition = true;
|
|
lp.x = mRectForFolderAnimation.left;
|
|
lp.y = mRectForFolderAnimation.top;
|
|
lp.width = (int) (scale * width);
|
|
lp.height = (int) (scale * height);
|
|
|
|
mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
|
|
fi.draw(mFolderIconCanvas);
|
|
mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
|
|
if (fi.getFolder() != null) {
|
|
mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
|
|
mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
|
|
}
|
|
// Just in case this image view is still in the drag layer from a previous animation,
|
|
// we remove it and re-add it.
|
|
if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
|
|
mDragLayer.removeView(mFolderIconImageView);
|
|
}
|
|
mDragLayer.addView(mFolderIconImageView, lp);
|
|
if (fi.getFolder() != null) {
|
|
fi.getFolder().bringToFront();
|
|
}
|
|
}
|
|
|
|
private void growAndFadeOutFolderIcon(FolderIcon fi) {
|
|
if (fi == null) return;
|
|
FolderInfo info = (FolderInfo) fi.getTag();
|
|
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
|
|
CellLayout cl = (CellLayout) fi.getParent().getParent();
|
|
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
|
|
cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
|
|
}
|
|
|
|
// Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
|
|
copyFolderIconToImage(fi);
|
|
fi.setVisibility(View.INVISIBLE);
|
|
|
|
ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(
|
|
mFolderIconImageView, 0, 1.5f, 1.5f);
|
|
if (Utilities.ATLEAST_LOLLIPOP) {
|
|
oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
}
|
|
oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
|
|
oa.start();
|
|
}
|
|
|
|
private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) {
|
|
if (fi == null) return;
|
|
final CellLayout cl = (CellLayout) fi.getParent().getParent();
|
|
|
|
// We remove and re-draw the FolderIcon in-case it has changed
|
|
mDragLayer.removeView(mFolderIconImageView);
|
|
copyFolderIconToImage(fi);
|
|
|
|
if (cl != null) {
|
|
cl.clearFolderLeaveBehind();
|
|
}
|
|
|
|
ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1);
|
|
oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
|
|
oa.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (cl != null) {
|
|
// Remove the ImageView copy of the FolderIcon and make the original visible.
|
|
mDragLayer.removeView(mFolderIconImageView);
|
|
fi.setVisibility(View.VISIBLE);
|
|
}
|
|
}
|
|
});
|
|
oa.start();
|
|
if (!animate) {
|
|
oa.end();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens the user folder described by the specified tag. The opening of the folder
|
|
* is animated relative to the specified View. If the View is null, no animation
|
|
* is played.
|
|
*
|
|
* @param folderIcon The FolderIcon describing the folder to open.
|
|
*/
|
|
public void openFolder(FolderIcon folderIcon) {
|
|
|
|
Folder folder = folderIcon.getFolder();
|
|
Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
|
|
if (openFolder != null && openFolder != folder) {
|
|
// Close any open folder before opening a folder.
|
|
closeFolder();
|
|
}
|
|
|
|
FolderInfo info = folder.mInfo;
|
|
|
|
info.opened = true;
|
|
|
|
// While the folder is open, the position of the icon cannot change.
|
|
((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
|
|
|
|
// Just verify that the folder hasn't already been added to the DragLayer.
|
|
// There was a one-off crash where the folder had a parent already.
|
|
if (folder.getParent() == null) {
|
|
mDragLayer.addView(folder);
|
|
mDragController.addDropTarget(folder);
|
|
} else {
|
|
Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
|
|
folder.getParent() + ").");
|
|
}
|
|
folder.animateOpen();
|
|
|
|
growAndFadeOutFolderIcon(folderIcon);
|
|
|
|
// Notify the accessibility manager that this folder "window" has appeared and occluded
|
|
// the workspace items
|
|
folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
|
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
|
|
}
|
|
|
|
public void closeFolder() {
|
|
closeFolder(true);
|
|
}
|
|
|
|
public void closeFolder(boolean animate) {
|
|
Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
|
|
if (folder != null) {
|
|
if (folder.isEditingName()) {
|
|
folder.dismissEditingName();
|
|
}
|
|
closeFolder(folder, animate);
|
|
}
|
|
}
|
|
|
|
public void closeFolder(Folder folder, boolean animate) {
|
|
animate &= !Utilities.isPowerSaverOn(this);
|
|
|
|
folder.getInfo().opened = false;
|
|
|
|
ViewGroup parent = (ViewGroup) folder.getParent().getParent();
|
|
if (parent != null) {
|
|
FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
|
|
shrinkAndFadeInFolderIcon(fi, animate);
|
|
if (fi != null) {
|
|
((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
|
|
}
|
|
}
|
|
if (animate) {
|
|
folder.animateClosed();
|
|
} else {
|
|
folder.close(false);
|
|
}
|
|
|
|
// Notify the accessibility manager that this folder "window" has disappeared and no
|
|
// longer occludes the workspace items
|
|
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
|
}
|
|
|
|
public void closeShortcutsContainer() {
|
|
closeShortcutsContainer(true);
|
|
}
|
|
|
|
public void closeShortcutsContainer(boolean animate) {
|
|
DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer();
|
|
if (deepShortcutsContainer != null) {
|
|
if (animate) {
|
|
deepShortcutsContainer.animateClose();
|
|
} else {
|
|
deepShortcutsContainer.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The open shortcuts container, or null if there is none
|
|
*/
|
|
public DeepShortcutsContainer getOpenShortcutsContainer() {
|
|
// Iterate in reverse order. Shortcuts container is added later to the dragLayer,
|
|
// and will be one of the last views.
|
|
for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) {
|
|
View child = mDragLayer.getChildAt(i);
|
|
if (child instanceof DeepShortcutsContainer
|
|
&& ((DeepShortcutsContainer) child).isOpen()) {
|
|
return (DeepShortcutsContainer) child;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean onLongClick(View v) {
|
|
if (!isDraggingEnabled()) return false;
|
|
if (isWorkspaceLocked()) return false;
|
|
if (mState != State.WORKSPACE) return false;
|
|
|
|
if ((FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && v instanceof PageIndicator) ||
|
|
(v == mAllAppsButton && mAllAppsButton != null)) {
|
|
onLongClickAllAppsButton(v);
|
|
return true;
|
|
}
|
|
|
|
if (v instanceof Workspace) {
|
|
if (!mWorkspace.isInOverviewMode()) {
|
|
if (!mWorkspace.isTouchActive()) {
|
|
showOverviewMode(true);
|
|
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
|
|
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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.
|
|
if (!mDragController.isDragging()) {
|
|
if (itemUnderLongClick == null) {
|
|
// User long pressed on empty space
|
|
mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
|
|
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
|
if (mWorkspace.isInOverviewMode()) {
|
|
mWorkspace.startReordering(v);
|
|
} else {
|
|
showOverviewMode(true);
|
|
mHotseat.requestDisallowInterceptTouchEvent(true);
|
|
}
|
|
} else {
|
|
final boolean isAllAppsButton =
|
|
!FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
|
|
mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat(
|
|
longClickCellInfo.cellX, longClickCellInfo.cellY));
|
|
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
|
|
// User long pressed on an item
|
|
mWorkspace.startDrag(longClickCellInfo, new DragOptions());
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
boolean isHotseatLayout(View layout) {
|
|
return mHotseat != null && layout != null &&
|
|
(layout instanceof CellLayout) && (layout == mHotseat.getLayout());
|
|
}
|
|
|
|
/**
|
|
* Returns the CellLayout of the specified container at the specified screen.
|
|
*/
|
|
public CellLayout getCellLayout(long container, long screenId) {
|
|
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
|
|
if (mHotseat != null) {
|
|
return mHotseat.getLayout();
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return mWorkspace.getScreenWithId(screenId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For overridden classes.
|
|
*/
|
|
public boolean isAllAppsVisible() {
|
|
return isAppsViewVisible();
|
|
}
|
|
|
|
public boolean isAppsViewVisible() {
|
|
return (mState == State.APPS) || (mOnResumeState == State.APPS);
|
|
}
|
|
|
|
public boolean isWidgetsViewVisible() {
|
|
return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
|
|
}
|
|
|
|
@Override
|
|
public void onTrimMemory(int level) {
|
|
super.onTrimMemory(level);
|
|
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
|
|
// The widget preview db can result in holding onto over
|
|
// 3MB of memory for caching which isn't necessary.
|
|
SQLiteDatabase.releaseMemory();
|
|
|
|
// This clears all widget bitmaps from the widget tray
|
|
// TODO(hyunyoungs)
|
|
}
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.onTrimMemory(level);
|
|
}
|
|
}
|
|
|
|
public boolean showWorkspace(boolean animated) {
|
|
return showWorkspace(animated, null);
|
|
}
|
|
|
|
public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) {
|
|
boolean changed = mState != State.WORKSPACE ||
|
|
mWorkspace.getState() != Workspace.State.NORMAL;
|
|
if (changed || mAllAppsController.isTransitioning()) {
|
|
mWorkspace.setVisibility(View.VISIBLE);
|
|
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
|
|
Workspace.State.NORMAL, animated, onCompleteRunnable);
|
|
|
|
// Set focus to the AppsCustomize button
|
|
if (mAllAppsButton != null) {
|
|
mAllAppsButton.requestFocus();
|
|
}
|
|
}
|
|
|
|
// Change the state *after* we've called all the transition code
|
|
mState = State.WORKSPACE;
|
|
|
|
// Resume the auto-advance of widgets
|
|
mUserPresent = true;
|
|
updateAutoAdvanceState();
|
|
|
|
if (changed) {
|
|
// Send an accessibility event to announce the context change
|
|
getWindow().getDecorView()
|
|
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* Shows the overview button.
|
|
*/
|
|
public void showOverviewMode(boolean animated) {
|
|
showOverviewMode(animated, false);
|
|
}
|
|
|
|
/**
|
|
* Shows the overview button, and if {@param requestButtonFocus} is set, will force the focus
|
|
* onto one of the overview panel buttons.
|
|
*/
|
|
void showOverviewMode(boolean animated, boolean requestButtonFocus) {
|
|
Runnable postAnimRunnable = null;
|
|
if (requestButtonFocus) {
|
|
postAnimRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
// Hitting the menu button when in touch mode does not trigger touch mode to
|
|
// be disabled, so if requested, force focus on one of the overview panel
|
|
// buttons.
|
|
mOverviewPanel.requestFocusFromTouch();
|
|
}
|
|
};
|
|
}
|
|
mWorkspace.setVisibility(View.VISIBLE);
|
|
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
|
|
Workspace.State.OVERVIEW, animated, postAnimRunnable);
|
|
mState = State.WORKSPACE;
|
|
}
|
|
|
|
/**
|
|
* Shows the apps view.
|
|
*/
|
|
public void showAppsView(boolean animated, boolean updatePredictedApps,
|
|
boolean focusSearchBar) {
|
|
markAppsViewShown();
|
|
if (updatePredictedApps) {
|
|
tryAndUpdatePredictedApps();
|
|
}
|
|
showAppsOrWidgets(State.APPS, animated, focusSearchBar);
|
|
}
|
|
|
|
/**
|
|
* Shows the widgets view.
|
|
*/
|
|
void showWidgetsView(boolean animated, boolean resetPageToZero) {
|
|
if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
|
|
if (resetPageToZero) {
|
|
mWidgetsView.scrollToTop();
|
|
}
|
|
showAppsOrWidgets(State.WIDGETS, animated, false);
|
|
|
|
mWidgetsView.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mWidgetsView.requestFocus();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets up the transition to show the apps/widgets view.
|
|
*
|
|
* @return whether the current from and to state allowed this operation
|
|
*/
|
|
// TODO: calling method should use the return value so that when {@code false} is returned
|
|
// the workspace transition doesn't fall into invalid state.
|
|
private boolean showAppsOrWidgets(State toState, boolean animated, boolean focusSearchBar) {
|
|
if (!(mState == State.WORKSPACE ||
|
|
mState == State.APPS_SPRING_LOADED ||
|
|
mState == State.WIDGETS_SPRING_LOADED ||
|
|
(mState == State.APPS && mAllAppsController.isTransitioning()))) {
|
|
return false;
|
|
}
|
|
if (toState != State.APPS && toState != State.WIDGETS) {
|
|
return false;
|
|
}
|
|
|
|
// This is a safe and supported transition to bypass spring_loaded mode.
|
|
if (mExitSpringLoadedModeRunnable != null) {
|
|
mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
|
|
mExitSpringLoadedModeRunnable = null;
|
|
}
|
|
|
|
if (toState == State.APPS) {
|
|
mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
|
|
focusSearchBar);
|
|
} else {
|
|
mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
|
|
}
|
|
|
|
// Change the state *after* we've called all the transition code
|
|
mState = toState;
|
|
|
|
// Pause the auto-advance of widgets until we are out of AllApps
|
|
mUserPresent = false;
|
|
updateAutoAdvanceState();
|
|
closeFolder();
|
|
closeShortcutsContainer();
|
|
|
|
// Send an accessibility event to announce the context change
|
|
getWindow().getDecorView()
|
|
.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Updates the workspace and interaction state on state change, and return the animation to this
|
|
* new state.
|
|
*/
|
|
public Animator startWorkspaceStateChangeAnimation(Workspace.State toState,
|
|
boolean animated, HashMap<View, Integer> layerViews) {
|
|
Workspace.State fromState = mWorkspace.getState();
|
|
Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews);
|
|
updateInteraction(fromState, toState);
|
|
return anim;
|
|
}
|
|
|
|
public void enterSpringLoadedDragMode() {
|
|
if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
|
|
if (isStateSpringLoaded()) {
|
|
return;
|
|
}
|
|
|
|
mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
|
|
Workspace.State.SPRING_LOADED, true /* animated */,
|
|
null /* onCompleteRunnable */);
|
|
|
|
if (isAppsViewVisible()) {
|
|
mState = State.APPS_SPRING_LOADED;
|
|
} else if (isWidgetsViewVisible()) {
|
|
mState = State.WIDGETS_SPRING_LOADED;
|
|
} else if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
|
|
mState = State.WORKSPACE_SPRING_LOADED;
|
|
} else {
|
|
mState = State.WORKSPACE;
|
|
}
|
|
}
|
|
|
|
public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
|
|
final Runnable onCompleteRunnable) {
|
|
if (!isStateSpringLoaded()) return;
|
|
|
|
if (mExitSpringLoadedModeRunnable != null) {
|
|
mHandler.removeCallbacks(mExitSpringLoadedModeRunnable);
|
|
}
|
|
mExitSpringLoadedModeRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (successfulDrop) {
|
|
// TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
|
|
//
|
|
// Before we show workspace, hide all apps again because
|
|
// exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
|
|
// clean up our state transition functions
|
|
mWidgetsView.setVisibility(View.GONE);
|
|
showWorkspace(true, onCompleteRunnable);
|
|
} else {
|
|
exitSpringLoadedDragMode();
|
|
}
|
|
mExitSpringLoadedModeRunnable = null;
|
|
}
|
|
};
|
|
mHandler.postDelayed(mExitSpringLoadedModeRunnable, delay);
|
|
}
|
|
|
|
boolean isStateSpringLoaded() {
|
|
return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
|
|
|| mState == State.WIDGETS_SPRING_LOADED;
|
|
}
|
|
|
|
void exitSpringLoadedDragMode() {
|
|
if (mState == State.APPS_SPRING_LOADED) {
|
|
showAppsView(true /* animated */,
|
|
false /* updatePredictedApps */, false /* focusSearchBar */);
|
|
} else if (mState == State.WIDGETS_SPRING_LOADED) {
|
|
showWidgetsView(true, false);
|
|
} else if (mState == State.WORKSPACE_SPRING_LOADED) {
|
|
showWorkspace(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
|
|
* resumed.
|
|
*/
|
|
public void tryAndUpdatePredictedApps() {
|
|
if (mLauncherCallbacks != null) {
|
|
List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps();
|
|
if (apps != null) {
|
|
mAppsView.setPredictedApps(apps);
|
|
getUserEventDispatcher().setPredictedApps(apps);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lockAllApps() {
|
|
// TODO
|
|
}
|
|
|
|
void unlockAllApps() {
|
|
// TODO
|
|
}
|
|
|
|
public boolean launcherCallbacksProvidesSearch() {
|
|
return (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch());
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
|
final boolean result = super.dispatchPopulateAccessibilityEvent(event);
|
|
final List<CharSequence> text = event.getText();
|
|
text.clear();
|
|
// Populate event with a fake title based on the current state.
|
|
if (mState == State.APPS) {
|
|
text.add(getString(R.string.all_apps_button_label));
|
|
} else if (mState == State.WIDGETS) {
|
|
text.add(getString(R.string.widget_button_text));
|
|
} else if (mWorkspace != null) {
|
|
text.add(mWorkspace.getCurrentPageDescription());
|
|
} else {
|
|
text.add(getString(R.string.all_apps_home_button_label));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* If the activity is currently paused, signal that we need to run the passed Runnable
|
|
* in onResume.
|
|
*
|
|
* This needs to be called from incoming places where resources might have been loaded
|
|
* while the activity is paused. That is because the Configuration (e.g., rotation) might be
|
|
* wrong when we're not running, and if the activity comes back to what the configuration was
|
|
* when we were paused, activity is not restarted.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*
|
|
* @return {@code true} if we are currently paused. The caller might be able to skip some work
|
|
*/
|
|
@Thunk boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
|
|
if (mPaused) {
|
|
if (LOGD) Log.d(TAG, "Deferring update until onResume");
|
|
if (deletePreviousRunnables) {
|
|
while (mBindOnResumeCallbacks.remove(run)) {
|
|
}
|
|
}
|
|
mBindOnResumeCallbacks.add(run);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean waitUntilResume(Runnable run) {
|
|
return waitUntilResume(run, false);
|
|
}
|
|
|
|
public void addOnResumeCallback(Runnable run) {
|
|
mOnResumeCallbacks.add(run);
|
|
}
|
|
|
|
/**
|
|
* If the activity is currently paused, signal that we need to re-run the loader
|
|
* in onResume.
|
|
*
|
|
* This needs to be called from incoming places where resources might have been loaded
|
|
* while we are paused. That is becaues the Configuration might be wrong
|
|
* when we're not running, and if it comes back to what it was when we
|
|
* were paused, we are not restarted.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*
|
|
* @return true if we are currently paused. The caller might be able to
|
|
* skip some work in that case since we will come back again.
|
|
*/
|
|
@Override
|
|
public boolean setLoadOnResume() {
|
|
if (mPaused) {
|
|
if (LOGD) Log.d(TAG, "setLoadOnResume");
|
|
mOnResumeNeedsLoad = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
@Override
|
|
public int getCurrentWorkspaceScreen() {
|
|
if (mWorkspace != null) {
|
|
return mWorkspace.getCurrentPage();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear any pending bind callbacks. This is called when is loader is planning to
|
|
* perform a full rebind from scratch.
|
|
*/
|
|
@Override
|
|
public void clearPendingBinds() {
|
|
mBindOnResumeCallbacks.clear();
|
|
if (mPendingExecutor != null) {
|
|
mPendingExecutor.markCompleted();
|
|
mPendingExecutor = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refreshes the shortcuts shown on the workspace.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
public void startBinding() {
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.beginSection("Starting page bind");
|
|
}
|
|
setWorkspaceLoading(true);
|
|
|
|
// Clear the workspace because it's going to be rebound
|
|
mWorkspace.clearDropTargets();
|
|
mWorkspace.removeAllWorkspaceScreens();
|
|
|
|
mWidgetsToAdvance.clear();
|
|
if (mHotseat != null) {
|
|
mHotseat.resetLayout();
|
|
}
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.endSection();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void bindScreens(ArrayList<Long> orderedScreenIds) {
|
|
// Make sure the first screen is always at the start.
|
|
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
|
|
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
|
|
orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
|
|
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
|
|
mModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
|
|
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
|
|
// If there are no screens, we need to have an empty screen
|
|
mWorkspace.addExtraEmptyScreen();
|
|
}
|
|
bindAddScreens(orderedScreenIds);
|
|
|
|
// Create the custom content page (this call updates mDefaultScreen which calls
|
|
// setCurrentPage() so ensure that all pages are added before calling this).
|
|
if (hasCustomContentToLeft()) {
|
|
mWorkspace.createCustomContentContainer();
|
|
populateCustomContentContainer();
|
|
}
|
|
|
|
// After we have added all the screens, if the wallpaper was locked to the default state,
|
|
// then notify to indicate that it can be released and a proper wallpaper offset can be
|
|
// computed before the next layout
|
|
mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
|
|
}
|
|
|
|
private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
|
|
int count = orderedScreenIds.size();
|
|
for (int i = 0; i < count; i++) {
|
|
long screenId = orderedScreenIds.get(i);
|
|
if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
|
|
// No need to bind the first screen, as its always bound.
|
|
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void bindAppsAdded(final ArrayList<Long> newScreens,
|
|
final ArrayList<ItemInfo> addNotAnimated,
|
|
final ArrayList<ItemInfo> addAnimated,
|
|
final ArrayList<AppInfo> addedApps) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
// Add the new screens
|
|
if (newScreens != null) {
|
|
bindAddScreens(newScreens);
|
|
}
|
|
|
|
// We add the items without animation on non-visible pages, and with
|
|
// animations on the new page (which we will try and snap to).
|
|
if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
|
|
bindItems(addNotAnimated, 0,
|
|
addNotAnimated.size(), false);
|
|
}
|
|
if (addAnimated != null && !addAnimated.isEmpty()) {
|
|
bindItems(addAnimated, 0,
|
|
addAnimated.size(), true);
|
|
}
|
|
|
|
// Remove the extra empty screen
|
|
mWorkspace.removeExtraEmptyScreen(false, false);
|
|
|
|
if (addedApps != null && mAppsView != null) {
|
|
mAppsView.addApps(addedApps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind the items start-end from the list.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
@Override
|
|
public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
|
|
final boolean forceAnimateIcons) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindItems(shortcuts, start, end, forceAnimateIcons);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
// Get the list of added shortcuts and intersect them with the set of shortcuts here
|
|
final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
|
|
final Collection<Animator> bounceAnims = new ArrayList<Animator>();
|
|
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
|
|
Workspace workspace = mWorkspace;
|
|
long newShortcutsScreenId = -1;
|
|
for (int i = start; i < end; i++) {
|
|
final ItemInfo item = shortcuts.get(i);
|
|
|
|
// Short circuit if we are loading dock items for a configuration which has no dock
|
|
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
|
|
mHotseat == null) {
|
|
continue;
|
|
}
|
|
|
|
final View view;
|
|
switch (item.itemType) {
|
|
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
|
|
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
|
|
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
|
|
ShortcutInfo info = (ShortcutInfo) item;
|
|
view = createShortcut(info);
|
|
break;
|
|
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
|
|
view = FolderIcon.fromXml(R.layout.folder_icon, this,
|
|
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
|
|
(FolderInfo) item, mIconCache);
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Invalid Item Type");
|
|
}
|
|
|
|
/*
|
|
* Remove colliding items.
|
|
*/
|
|
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
|
|
CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
|
|
if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
|
|
View v = cl.getChildAt(item.cellX, item.cellY);
|
|
Object tag = v.getTag();
|
|
String desc = "Collision while binding workspace item: " + item
|
|
+ ". Collides with " + tag;
|
|
if (ProviderConfig.IS_DOGFOOD_BUILD) {
|
|
throw (new RuntimeException(desc));
|
|
} else {
|
|
Log.d(TAG, desc);
|
|
LauncherModel.deleteItemFromDatabase(this, item);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
|
|
item.cellY, 1, 1);
|
|
if (animateIcons) {
|
|
// Animate all the applications up now
|
|
view.setAlpha(0f);
|
|
view.setScaleX(0f);
|
|
view.setScaleY(0f);
|
|
bounceAnims.add(createNewAppBounceAnimation(view, i));
|
|
newShortcutsScreenId = item.screenId;
|
|
}
|
|
}
|
|
|
|
if (animateIcons) {
|
|
// Animate to the correct page
|
|
if (newShortcutsScreenId > -1) {
|
|
long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
|
|
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
|
|
final Runnable startBounceAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
anim.playTogether(bounceAnims);
|
|
anim.start();
|
|
}
|
|
};
|
|
if (newShortcutsScreenId != currentScreenId) {
|
|
// We post the animation slightly delayed to prevent slowdowns
|
|
// when we are loading right after we return to launcher.
|
|
mWorkspace.postDelayed(new Runnable() {
|
|
public void run() {
|
|
if (mWorkspace != null) {
|
|
mWorkspace.snapToPage(newScreenIndex);
|
|
mWorkspace.postDelayed(startBounceAnimRunnable,
|
|
NEW_APPS_ANIMATION_DELAY);
|
|
}
|
|
}
|
|
}, NEW_APPS_PAGE_MOVE_DELAY);
|
|
} else {
|
|
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
|
|
}
|
|
}
|
|
}
|
|
workspace.requestLayout();
|
|
}
|
|
|
|
private void bindSafeModeWidget(LauncherAppWidgetInfo item) {
|
|
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, true);
|
|
view.updateIcon(mIconCache);
|
|
view.updateAppWidget(null);
|
|
view.setOnClickListener(this);
|
|
addAppWidgetToWorkspace(view, item, null, false);
|
|
mWorkspace.requestLayout();
|
|
}
|
|
|
|
/**
|
|
* Add the views for a widget to the workspace.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
public void bindAppWidget(final LauncherAppWidgetInfo item) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindAppWidget(item);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
if (mIsSafeModeEnabled) {
|
|
bindSafeModeWidget(item);
|
|
return;
|
|
}
|
|
|
|
final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
|
|
if (DEBUG_WIDGETS) {
|
|
Log.d(TAG, "bindAppWidget: " + item);
|
|
}
|
|
|
|
final LauncherAppWidgetProviderInfo appWidgetInfo;
|
|
|
|
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
|
|
// If the provider is not ready, bind as a pending widget.
|
|
appWidgetInfo = null;
|
|
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
|
|
// The widget id is not valid. Try to find the widget based on the provider info.
|
|
appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
|
|
} else {
|
|
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
|
|
}
|
|
|
|
// If the provider is ready, but the width is not yet restored, try to restore it.
|
|
if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) &&
|
|
(item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
|
|
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;
|
|
}
|
|
|
|
// If we do not have a valid id, try to bind an id.
|
|
if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
|
|
if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
|
|
// Id has not been allocated yet. Allocate a new id.
|
|
item.appWidgetId = mAppWidgetHost.allocateAppWidgetId();
|
|
item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
|
|
|
|
// Also try to bind the widget. If the bind fails, the user will be shown
|
|
// a click to setup UI, which will ask for the bind permission.
|
|
PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo);
|
|
pendingInfo.spanX = item.spanX;
|
|
pendingInfo.spanY = item.spanY;
|
|
pendingInfo.minSpanX = item.minSpanX;
|
|
pendingInfo.minSpanY = item.minSpanY;
|
|
Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
|
|
|
|
boolean isDirectConfig =
|
|
item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
|
|
if (isDirectConfig && item.bindOptions != null) {
|
|
Bundle newOptions = item.bindOptions.getExtras();
|
|
if (options != null) {
|
|
newOptions.putAll(options);
|
|
}
|
|
options = newOptions;
|
|
}
|
|
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
|
|
item.appWidgetId, appWidgetInfo, options);
|
|
|
|
// We tried to bind once. If we were not able to bind, we would need to
|
|
// go through the permission dialog, which means we cannot skip the config
|
|
// activity.
|
|
item.bindOptions = null;
|
|
item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
|
|
|
|
// Bind succeeded
|
|
if (success) {
|
|
// 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) || isDirectConfig
|
|
? LauncherAppWidgetInfo.RESTORE_COMPLETED
|
|
: LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
|
|
}
|
|
|
|
LauncherModel.updateItemInDatabase(this, item);
|
|
}
|
|
} else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
|
|
&& (appWidgetInfo.configure == null)) {
|
|
// The widget was marked as UI not ready, but there is no configure activity to
|
|
// update the UI.
|
|
item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
|
|
LauncherModel.updateItemInDatabase(this, item);
|
|
}
|
|
}
|
|
|
|
if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
|
|
if (DEBUG_WIDGETS) {
|
|
Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
|
|
+ appWidgetInfo.provider);
|
|
}
|
|
|
|
// Verify that we own the widget
|
|
if (appWidgetInfo == null) {
|
|
FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
|
|
deleteWidgetInfo(item);
|
|
return;
|
|
}
|
|
|
|
item.minSpanX = appWidgetInfo.minSpanX;
|
|
item.minSpanY = appWidgetInfo.minSpanY;
|
|
addAppWidgetToWorkspace(
|
|
mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo),
|
|
item, appWidgetInfo, false);
|
|
} else {
|
|
PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, false);
|
|
view.updateIcon(mIconCache);
|
|
view.updateAppWidget(null);
|
|
view.setOnClickListener(this);
|
|
addAppWidgetToWorkspace(view, item, null, false);
|
|
}
|
|
mWorkspace.requestLayout();
|
|
|
|
if (DEBUG_WIDGETS) {
|
|
Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
|
|
+ (SystemClock.uptimeMillis()-start) + "ms");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restores a pending widget.
|
|
*
|
|
* @param appWidgetId The app widget id
|
|
*/
|
|
private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
|
|
LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
|
|
if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
|
|
Log.e(TAG, "Widget update called, when the widget no longer exists.");
|
|
return null;
|
|
}
|
|
|
|
LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
|
|
info.restoreStatus = finalRestoreFlag;
|
|
|
|
mWorkspace.reinflateWidgetsIfNecessary();
|
|
LauncherModel.updateItemInDatabase(this, info);
|
|
return info;
|
|
}
|
|
|
|
public void onPageBoundSynchronously(int page) {
|
|
mSynchronouslyBoundPages.add(page);
|
|
}
|
|
|
|
@Override
|
|
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
|
|
if (mPendingExecutor != null) {
|
|
mPendingExecutor.markCompleted();
|
|
}
|
|
mPendingExecutor = executor;
|
|
executor.attachTo(this);
|
|
}
|
|
|
|
public void clearPendingExecutor(ViewOnDrawExecutor executor) {
|
|
if (mPendingExecutor == executor) {
|
|
mPendingExecutor = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
finishFirstPageBind(executor);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
Runnable onComplete = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (executor != null) {
|
|
executor.onLoadAnimationCompleted();
|
|
}
|
|
}
|
|
};
|
|
if (mDragLayer.getAlpha() < 1) {
|
|
mDragLayer.animate().alpha(1).withEndAction(onComplete).start();
|
|
} else {
|
|
onComplete.run();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback saying that there aren't any more items to bind.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
public void finishBindingItems() {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
finishBindingItems();
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.beginSection("Page bind completed");
|
|
}
|
|
if (mSavedState != null) {
|
|
if (!mWorkspace.hasFocus()) {
|
|
mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
|
|
}
|
|
|
|
mSavedState = null;
|
|
}
|
|
|
|
mWorkspace.restoreInstanceStateForRemainingPages();
|
|
|
|
setWorkspaceLoading(false);
|
|
|
|
// 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.
|
|
if (sPendingAddItem != null) {
|
|
final long screenId = completeAdd(sPendingAddItem);
|
|
|
|
// 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;
|
|
}
|
|
|
|
InstallShortcutReceiver.disableAndFlushInstallQueue(this);
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.finishBindingItems(false);
|
|
}
|
|
if (LauncherAppState.PROFILE_STARTUP) {
|
|
Trace.endSection();
|
|
}
|
|
}
|
|
|
|
private boolean canRunNewAppsAnimation() {
|
|
long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
|
|
return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
|
|
}
|
|
|
|
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
|
|
ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1);
|
|
bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
|
|
bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
|
|
bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
|
|
return bounceAnim;
|
|
}
|
|
|
|
public boolean useVerticalBarLayout() {
|
|
return mDeviceProfile.isVerticalBarLayout();
|
|
}
|
|
|
|
public int getSearchBarHeight() {
|
|
if (mLauncherCallbacks != null) {
|
|
return mLauncherCallbacks.getSearchBarHeight();
|
|
}
|
|
return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL;
|
|
}
|
|
|
|
/**
|
|
* A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
|
|
* multiple calls to bind the same list.)
|
|
*/
|
|
@Thunk ArrayList<AppInfo> mTmpAppsList;
|
|
private Runnable mBindAllApplicationsRunnable = new Runnable() {
|
|
public void run() {
|
|
bindAllApplications(mTmpAppsList);
|
|
mTmpAppsList = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add the icons for all apps.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
public void bindAllApplications(final ArrayList<AppInfo> apps) {
|
|
if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
|
|
mTmpAppsList = apps;
|
|
return;
|
|
}
|
|
|
|
if (mAppsView != null) {
|
|
mAppsView.setApps(apps);
|
|
}
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.bindAllApplications(apps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
|
|
* because LauncherModel's map is updated in the background, while Launcher runs on the UI.
|
|
*/
|
|
@Override
|
|
public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
|
|
mDeepShortcutMap = deepShortcutMapCopy;
|
|
if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
|
|
}
|
|
|
|
public List<String> getShortcutIdsForItem(ItemInfo info) {
|
|
if (!DeepShortcutManager.supportsShortcuts(info)) {
|
|
return Collections.EMPTY_LIST;
|
|
}
|
|
ComponentName component = info.getTargetComponent();
|
|
if (component == null) {
|
|
return Collections.EMPTY_LIST;
|
|
}
|
|
|
|
List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
|
|
return ids == null ? Collections.EMPTY_LIST : ids;
|
|
}
|
|
|
|
/**
|
|
* A package was updated.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindAppsUpdated(apps);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
if (mAppsView != null) {
|
|
mAppsView.updateApps(apps);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindWidgetsRestored(widgets);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
mWorkspace.widgetsRestored(widgets);
|
|
}
|
|
|
|
/**
|
|
* Some shortcuts were updated in the background.
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*
|
|
* @param updated list of shortcuts which have changed.
|
|
* @param removed list of shortcuts which were deleted in the background. This can happen when
|
|
* an app gets removed from the system or some of its components are no longer
|
|
* available.
|
|
*/
|
|
@Override
|
|
public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
|
|
final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindShortcutsChanged(updated, removed, user);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
if (!updated.isEmpty()) {
|
|
mWorkspace.updateShortcuts(updated);
|
|
}
|
|
|
|
if (!removed.isEmpty()) {
|
|
HashSet<ComponentName> removedComponents = new HashSet<>();
|
|
HashSet<ShortcutKey> removedDeepShortcuts = new HashSet<>();
|
|
|
|
for (ShortcutInfo si : removed) {
|
|
if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
|
removedDeepShortcuts.add(ShortcutKey.fromShortcutInfo(si));
|
|
} else {
|
|
removedComponents.add(si.getTargetComponent());
|
|
}
|
|
}
|
|
|
|
if (!removedComponents.isEmpty()) {
|
|
ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(removedComponents, user);
|
|
mWorkspace.removeItemsByMatcher(matcher);
|
|
mDragController.onAppsRemoved(matcher);
|
|
}
|
|
|
|
if (!removedDeepShortcuts.isEmpty()) {
|
|
ItemInfoMatcher matcher = ItemInfoMatcher.ofShortcutKeys(removedDeepShortcuts);
|
|
mWorkspace.removeItemsByMatcher(matcher);
|
|
mDragController.onAppsRemoved(matcher);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the state of a package, typically related to install state.
|
|
*
|
|
* Implementation of the method from LauncherModel.Callbacks.
|
|
*/
|
|
@Override
|
|
public void bindRestoreItemsChange(final HashSet<ItemInfo> updates) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindRestoreItemsChange(updates);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
mWorkspace.updateRestoreItems(updates);
|
|
}
|
|
|
|
/**
|
|
* A package was uninstalled/updated. 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,
|
|
* we only remove specific components from the workspace and hotseat, where as
|
|
* package-removal should clear all items by package name.
|
|
*/
|
|
@Override
|
|
public void bindWorkspaceComponentsRemoved(
|
|
final HashSet<String> packageNames, final HashSet<ComponentName> components,
|
|
final UserHandleCompat user) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindWorkspaceComponentsRemoved(packageNames, components, user);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
if (!packageNames.isEmpty()) {
|
|
ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageNames, user);
|
|
mWorkspace.removeItemsByMatcher(matcher);
|
|
mDragController.onAppsRemoved(matcher);
|
|
|
|
}
|
|
if (!components.isEmpty()) {
|
|
ItemInfoMatcher matcher = ItemInfoMatcher.ofComponents(components, user);
|
|
mWorkspace.removeItemsByMatcher(matcher);
|
|
mDragController.onAppsRemoved(matcher);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
|
|
Runnable r = new Runnable() {
|
|
public void run() {
|
|
bindAppInfosRemoved(appInfos);
|
|
}
|
|
};
|
|
if (waitUntilResume(r)) {
|
|
return;
|
|
}
|
|
|
|
// Update AllApps
|
|
if (mAppsView != null) {
|
|
mAppsView.removeApps(appInfos);
|
|
}
|
|
}
|
|
|
|
private Runnable mBindWidgetModelRunnable = new Runnable() {
|
|
public void run() {
|
|
bindWidgetsModel(mWidgetsModel);
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void bindWidgetsModel(WidgetsModel model) {
|
|
if (waitUntilResume(mBindWidgetModelRunnable, true)) {
|
|
mWidgetsModel = model;
|
|
return;
|
|
}
|
|
|
|
if (mWidgetsView != null && model != null) {
|
|
mWidgetsView.addWidgets(model);
|
|
mWidgetsModel = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void notifyWidgetProvidersChanged() {
|
|
if (mWorkspace.getState().shouldUpdateWidget) {
|
|
mModel.refreshAndBindWidgetsAndShortcuts(this, mWidgetsView.isEmpty());
|
|
}
|
|
}
|
|
|
|
private int mapConfigurationOriActivityInfoOri(int configOri) {
|
|
final Display d = getWindowManager().getDefaultDisplay();
|
|
int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
|
|
switch (d.getRotation()) {
|
|
case Surface.ROTATION_0:
|
|
case Surface.ROTATION_180:
|
|
// We are currently in the same basic orientation as the natural orientation
|
|
naturalOri = configOri;
|
|
break;
|
|
case Surface.ROTATION_90:
|
|
case Surface.ROTATION_270:
|
|
// We are currently in the other basic orientation to the natural orientation
|
|
naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
|
|
Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
|
|
break;
|
|
}
|
|
|
|
int[] oriMap = {
|
|
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
|
|
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
|
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
|
|
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
};
|
|
// Since the map starts at portrait, we need to offset if this device's natural orientation
|
|
// is landscape.
|
|
int indexOffset = 0;
|
|
if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
|
|
indexOffset = 1;
|
|
}
|
|
return oriMap[(d.getRotation() + indexOffset) % 4];
|
|
}
|
|
|
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
|
public void lockScreenOrientation() {
|
|
if (mRotationEnabled) {
|
|
if (Utilities.ATLEAST_JB_MR2) {
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
|
} else {
|
|
setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
|
|
.getConfiguration().orientation));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void unlockScreenOrientation(boolean immediate) {
|
|
if (mRotationEnabled) {
|
|
if (immediate) {
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
} else {
|
|
mHandler.postDelayed(new Runnable() {
|
|
public void run() {
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
}
|
|
}, RESTORE_SCREEN_ORIENTATION_DELAY);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void markAppsViewShown() {
|
|
if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
|
|
return;
|
|
}
|
|
mSharedPrefs.edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
|
|
}
|
|
|
|
private boolean shouldShowDiscoveryBounce() {
|
|
if (mState != mState.WORKSPACE) {
|
|
return false;
|
|
}
|
|
if (mLauncherCallbacks != null && mLauncherCallbacks.shouldShowDiscoveryBounce()) {
|
|
return true;
|
|
}
|
|
if (!mIsResumeFromActionScreenOff) {
|
|
return false;
|
|
}
|
|
if (mSharedPrefs.getBoolean(APPS_VIEW_SHOWN, false)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// TODO: These method should be a part of LauncherSearchCallback
|
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
|
|
// Called from search suggestion
|
|
UserHandleCompat user = null;
|
|
if (Utilities.ATLEAST_LOLLIPOP) {
|
|
UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
|
|
if (userHandle != null) {
|
|
user = UserHandleCompat.fromUser(userHandle);
|
|
}
|
|
}
|
|
return createAppDragInfo(appLaunchIntent, user);
|
|
}
|
|
|
|
// TODO: This method should be a part of LauncherSearchCallback
|
|
public ItemInfo createAppDragInfo(Intent intent, UserHandleCompat user) {
|
|
if (user == null) {
|
|
user = UserHandleCompat.myUserHandle();
|
|
}
|
|
|
|
// Called from search suggestion, add the profile extra to the intent to ensure that we
|
|
// can launch it correctly
|
|
LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
|
|
LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(intent, user);
|
|
if (activityInfo == null) {
|
|
return null;
|
|
}
|
|
return new AppInfo(this, activityInfo, user, mIconCache);
|
|
}
|
|
|
|
// TODO: This method should be a part of LauncherSearchCallback
|
|
public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
|
|
Bitmap icon) {
|
|
return new ShortcutInfo(shortcutIntent, caption, caption, icon,
|
|
UserHandleCompat.myUserHandle());
|
|
}
|
|
|
|
protected void moveWorkspaceToDefaultScreen() {
|
|
mWorkspace.moveToDefaultScreen(false);
|
|
}
|
|
|
|
/**
|
|
* Returns a FastBitmapDrawable with the icon, accurately sized.
|
|
*/
|
|
public FastBitmapDrawable createIconDrawable(Bitmap icon) {
|
|
FastBitmapDrawable d = new FastBitmapDrawable(icon);
|
|
d.setFilterBitmap(true);
|
|
resizeIconDrawable(d);
|
|
return d;
|
|
}
|
|
|
|
/**
|
|
* Resizes an icon drawable to the correct icon size.
|
|
*/
|
|
public Drawable resizeIconDrawable(Drawable icon) {
|
|
icon.setBounds(0, 0, mDeviceProfile.iconSizePx, mDeviceProfile.iconSizePx);
|
|
return icon;
|
|
}
|
|
|
|
/**
|
|
* Prints out out state for debugging.
|
|
*/
|
|
public void dumpState() {
|
|
Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
|
|
Log.d(TAG, "mSavedState=" + mSavedState);
|
|
Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
|
|
Log.d(TAG, "mRestoring=" + mRestoring);
|
|
Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
|
|
mModel.dumpState();
|
|
// TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
|
|
|
|
Log.d(TAG, "END launcher3 dump state");
|
|
}
|
|
|
|
@Override
|
|
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
|
|
super.dump(prefix, fd, writer, args);
|
|
// Dump workspace
|
|
writer.println(prefix + "Workspace Items");
|
|
for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) {
|
|
writer.println(prefix + " Homescreen " + i);
|
|
|
|
ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
|
|
for (int j = 0; j < layout.getChildCount(); j++) {
|
|
Object tag = layout.getChildAt(j).getTag();
|
|
if (tag != null) {
|
|
writer.println(prefix + " " + tag.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
writer.println(prefix + " Hotseat");
|
|
ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets();
|
|
for (int j = 0; j < layout.getChildCount(); j++) {
|
|
Object tag = layout.getChildAt(j).getTag();
|
|
if (tag != null) {
|
|
writer.println(prefix + " " + tag.toString());
|
|
}
|
|
}
|
|
|
|
try {
|
|
FileLog.flushAll(writer);
|
|
} catch (Exception e) {
|
|
// Ignore
|
|
}
|
|
|
|
if (mLauncherCallbacks != null) {
|
|
mLauncherCallbacks.dump(prefix, fd, writer, args);
|
|
}
|
|
}
|
|
|
|
public static CustomAppWidget getCustomAppWidget(String name) {
|
|
return sCustomAppWidgets.get(name);
|
|
}
|
|
|
|
public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
|
|
return sCustomAppWidgets;
|
|
}
|
|
|
|
public static List<View> getFolderContents(View icon) {
|
|
if (icon instanceof FolderIcon) {
|
|
return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
|
|
} else {
|
|
return Collections.EMPTY_LIST;
|
|
}
|
|
}
|
|
|
|
public static Launcher getLauncher(Context context) {
|
|
if (context instanceof Launcher) {
|
|
return (Launcher) context;
|
|
}
|
|
return ((Launcher) ((ContextWrapper) context).getBaseContext());
|
|
}
|
|
|
|
private class RotationPrefChangeHandler implements OnSharedPreferenceChangeListener, Runnable {
|
|
|
|
@Override
|
|
public void onSharedPreferenceChanged(
|
|
SharedPreferences sharedPreferences, String key) {
|
|
if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
|
|
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
|
|
if (!waitUntilResume(this, true)) {
|
|
run();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
setOrientation();
|
|
}
|
|
}
|
|
}
|