blob: 439227f52303c13002c3a3034f6c6d0ac565929c [file] [log] [blame]
/*
* Copyright (C) 2015 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.widget;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.State;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.android.launcher3.BaseContainerView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeleteDropTarget;
import com.android.launcher3.DragController;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Folder;
import com.android.launcher3.IconCache;
import com.android.launcher3.Insettable;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.Workspace;
import java.util.ArrayList;
/**
* The widgets list view container.
*/
public class WidgetsContainerView extends BaseContainerView
implements View.OnLongClickListener, View.OnClickListener, DragSource{
private static final String TAG = "WidgetsContainerView";
private static final boolean DEBUG = false;
private static final int SPRING_MODE_DELAY_MS = 150;
/* Coefficient multiplied to the screen height for preloading widgets. */
private static final int PRELOAD_SCREEN_HEIGHT_MULTIPLE = 1;
/* Global instances that are used inside this container. */
private Launcher mLauncher;
private DragController mDragController;
private IconCache mIconCache;
/* Data model for the widget */
private WidgetsModel mWidgets;
/* Recycler view related member variables */
private RecyclerView mView;
private WidgetsListAdapter mAdapter;
/* Touch handling related member variables. */
private Toast mWidgetInstructionToast;
/* Rendering related. */
private WidgetPreviewLoader mWidgetPreviewLoader;
private WidgetHostViewLoader mWidgetHostViewLoader;
private Rect mPadding = new Rect();
public WidgetsContainerView(Context context) {
this(context, null);
}
public WidgetsContainerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = (Launcher) context;
mDragController = mLauncher.getDragController();
mWidgetHostViewLoader = new WidgetHostViewLoader(mLauncher);
mAdapter = new WidgetsListAdapter(context, this, this, mLauncher);
mWidgets = new WidgetsModel(context, mAdapter);
mAdapter.setWidgetsModel(mWidgets);
mIconCache = (LauncherAppState.getInstance()).getIconCache();
if (DEBUG) {
Log.d(TAG, "WidgetsContainerView constructor");
}
}
@Override
protected void onFinishInflate() {
if (DEBUG) {
Log.d(TAG, String.format("onFinishInflate [widgets size=%d]",
mWidgets.getPackageSize()));
}
mView = (RecyclerView) findViewById(R.id.widgets_list_view);
mView.setAdapter(mAdapter);
// This extends the layout space so that preloading happen for the {@link RecyclerView}
mView.setLayoutManager(new LinearLayoutManager(getContext()) {
@Override
protected int getExtraLayoutSpace(State state) {
return super.getExtraLayoutSpace(state)
+ WidgetsContainerView.this.getHeight() * PRELOAD_SCREEN_HEIGHT_MULTIPLE;
}
});
mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
getPaddingBottom());
onUpdatePaddings();
}
//
// Returns views used for launcher transitions.
//
public View getContentView() {
return findViewById(R.id.widgets_content);
}
public View getRevealView() {
// TODO(hyunyoungs): temporarily use apps view transition.
return findViewById(R.id.widgets_reveal_view);
}
public void scrollToTop() {
mView.scrollToPosition(0);
if (DEBUG) {
Log.d(TAG, String.format("scrollToTop, [widgets size=%d]",
mWidgets.getPackageSize()));
}
}
//
// Touch related handling.
//
@Override
public void onClick(View v) {
// When we have exited widget tray or are in transition, disregard clicks
if (!mLauncher.isWidgetsViewVisible()
|| mLauncher.getWorkspace().isSwitchingState()
|| !(v instanceof WidgetCell)) return;
// Let the user know that they have to long press to add a widget
if (mWidgetInstructionToast != null) {
mWidgetInstructionToast.cancel();
}
mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
Toast.LENGTH_SHORT);
mWidgetInstructionToast.show();
}
@Override
public boolean onLongClick(View v) {
if (DEBUG) {
Log.d(TAG, String.format("onLonglick [v=%s]", v));
}
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
// When we have exited all apps or are in transition, disregard long clicks
if (!mLauncher.isWidgetsViewVisible() ||
mLauncher.getWorkspace().isSwitchingState()) return false;
// Return if global dragging is not enabled
Log.d(TAG, String.format("onLonglick dragging enabled?.", v));
if (!mLauncher.isDraggingEnabled()) return false;
boolean status = beginDragging(v);
if (status) {
mWidgetHostViewLoader.load(v);
}
return status;
}
private boolean beginDragging(View v) {
if (v instanceof WidgetCell) {
if (!beginDraggingWidget((WidgetCell) v)) {
return false;
}
} else {
Log.e(TAG, "Unexpected dragging view: " + v);
}
// We delay entering spring-loaded mode slightly to make sure the UI
// thread is free of any work.
postDelayed(new Runnable() {
@Override
public void run() {
// We don't enter spring-loaded mode if the drag has been cancelled
if (mLauncher.getDragController().isDragging()) {
// Go into spring loaded mode (must happen before we startDrag())
mLauncher.enterSpringLoadedDragMode();
}
}
}, SPRING_MODE_DELAY_MS);
return true;
}
private boolean beginDraggingWidget(WidgetCell v) {
// Get the widget preview as the drag representation
ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
// we abort the drag.
if (image.getDrawable() == null) {
return false;
}
// Compose the drag image
Bitmap preview;
Bitmap outline;
float scale = 1f;
Point previewPadding = null;
if (createItemInfo instanceof PendingAddWidgetInfo) {
// This can happen in some weird cases involving multi-touch. We can't start dragging
// the widget if this is null, so we break out.
PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
float minScale = 1.25f;
int maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
int[] previewSizeBeforeScale = new int[1];
preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
maxWidth, null, previewSizeBeforeScale);
// Compare the size of the drag preview to the preview in the AppsCustomize tray
int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
v.getActualItemWidth());
scale = previewWidthInAppsCustomize / (float) preview.getWidth();
// The bitmap in the AppsCustomize tray is always the the same size, so there
// might be extra pixels around the preview itself - this accounts for that
if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
int padding =
(previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
previewPadding = new Point(padding, 0);
}
} else {
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
preview = Utilities.createIconBitmap(icon, mLauncher);
createItemInfo.spanX = createItemInfo.spanY = 1;
}
// Don't clip alpha values for the drag outline if we're using the default widget preview
boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
(((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
// Save the preview for the outline generation, then dim the preview
outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
false);
// Start the drag
mLauncher.lockScreenOrientation();
mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
mDragController.startDrag(image, preview, this, createItemInfo,
DragController.DRAG_ACTION_COPY, previewPadding, scale);
outline.recycle();
preview.recycle();
return true;
}
//
// Drag related handling methods that implement {@link DragSource} interface.
//
@Override
public boolean supportsFlingToDelete() {
return false;
}
@Override
public boolean supportsAppInfoDropTarget() {
return true;
}
/*
* Both this method and {@link #supportsFlingToDelete} has to return {@code false} for the
* {@link DeleteDropTarget} to be invisible.)
*/
@Override
public boolean supportsDeleteDropTarget() {
return false;
}
@Override
public float getIntrinsicIconScaleFactor() {
return 0;
}
@Override
public void onFlingToDeleteCompleted() {
// We just dismiss the drag when we fling, so cleanup here
mLauncher.exitSpringLoadedDragModeDelayed(true,
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
mLauncher.unlockScreenOrientation(false);
}
@Override
public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
boolean success) {
if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
!(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
// Exit spring loaded mode if we have not successfully dropped or have not handled the
// drop in Workspace
mLauncher.exitSpringLoadedDragModeDelayed(true,
Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
}
mLauncher.unlockScreenOrientation(false);
// Display an error message if the drag failed due to there not being enough space on the
// target layout we were dropping on.
if (!success) {
boolean showOutOfSpaceMessage = false;
if (target instanceof Workspace) {
int currentScreen = mLauncher.getCurrentWorkspaceScreen();
Workspace workspace = (Workspace) target;
CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
ItemInfo itemInfo = (ItemInfo) d.dragInfo;
if (layout != null) {
layout.calculateSpans(itemInfo);
showOutOfSpaceMessage =
!layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
}
}
if (showOutOfSpaceMessage) {
mLauncher.showOutOfSpaceMessage(false);
}
d.deferDragViewCleanupPostAnimation = false;
}
//TODO(hyunyoungs): if drop fails, this call cleans up correctly.
// However, in rare corner case where drop succeeds but doesn't end up using the widget
// id created by the loader, this finish will leave dangling widget id.
mWidgetHostViewLoader.finish(success);
}
//
// Container rendering related.
//
@Override
protected void onUpdatePaddings() {
if (mFixedBounds.isEmpty()) {
// If there are no fixed bounds, then use the default padding and insets
setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top,
mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom);
} else {
// If there are fixed bounds, then we update the padding to reflect the fixed bounds.
setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
mInsets.bottom);
}
}
/**
* Initialize the widget data model.
*/
public void addWidgets(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
mWidgets.addWidgetsAndShortcuts(widgetsShortcuts, pm);
}
private WidgetPreviewLoader getWidgetPreviewLoader() {
if (mWidgetPreviewLoader == null) {
mWidgetPreviewLoader = LauncherAppState.getInstance().getWidgetCache();
}
return mWidgetPreviewLoader;
}
}