| /* |
| * 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.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Rect; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnLayoutChangeListener; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| |
| import com.android.launcher3.DeviceProfile; |
| import com.android.launcher3.FastBitmapDrawable; |
| import com.android.launcher3.ItemInfo; |
| import com.android.launcher3.LauncherAppState; |
| import com.android.launcher3.LauncherAppWidgetProviderInfo; |
| import com.android.launcher3.R; |
| import com.android.launcher3.WidgetPreviewLoader; |
| import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; |
| import com.android.launcher3.compat.AppWidgetManagerCompat; |
| |
| /** |
| * The linear layout used strictly for the widget tray. |
| */ |
| public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { |
| |
| private static final String TAG = "WidgetCell"; |
| private static final boolean DEBUG = false; |
| |
| // Temporary preset width and height of the image to keep them aligned. |
| //private static final int PRESET_PREVIEW_HEIGHT = 480; |
| //private static final int PRESET_PREVIEW_WIDTH = 480; |
| |
| private int mPresetPreviewSize; |
| |
| private static WidgetCell sShortpressTarget = null; |
| |
| private final Rect mOriginalImagePadding = new Rect(); |
| |
| private String mDimensionsFormatString; |
| private CheckForShortPress mPendingCheckForShortPress = null; |
| private ShortPressListener mShortPressListener = null; |
| private boolean mShortPressTriggered = false; |
| private boolean mIsAppWidget; |
| private Object mInfo; |
| |
| private WidgetPreviewLoader mWidgetPreviewLoader; |
| private PreviewLoadRequest mActiveRequest; |
| |
| public WidgetCell(Context context) { |
| this(context, null); |
| } |
| |
| public WidgetCell(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public WidgetCell(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| final Resources r = context.getResources(); |
| mDimensionsFormatString = r.getString(R.string.widget_dims_format); |
| mPresetPreviewSize = r.getDimensionPixelSize(R.dimen.widget_preview_size); |
| |
| setWillNotDraw(false); |
| setClipToPadding(false); |
| setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); |
| |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| final ImageView image = (ImageView) findViewById(R.id.widget_preview); |
| mOriginalImagePadding.left = image.getPaddingLeft(); |
| mOriginalImagePadding.top = image.getPaddingTop(); |
| mOriginalImagePadding.right = image.getPaddingRight(); |
| mOriginalImagePadding.bottom = image.getPaddingBottom(); |
| |
| // Ensure we are using the right text size |
| LauncherAppState app = LauncherAppState.getInstance(); |
| DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); |
| TextView name = (TextView) findViewById(R.id.widget_name); |
| if (name != null) { |
| name.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); |
| } |
| TextView dims = (TextView) findViewById(R.id.widget_dims); |
| if (dims != null) { |
| dims.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); |
| } |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| if (DEBUG) { |
| Log.d(TAG, String.format("[tag=%s] onDetachedFromWindow", getTagToString())); |
| } |
| super.onDetachedFromWindow(); |
| deletePreview(false); |
| } |
| |
| public void reset() { |
| ImageView image = (ImageView) findViewById(R.id.widget_preview); |
| final TextView name = (TextView) findViewById(R.id.widget_name); |
| final TextView dims = (TextView) findViewById(R.id.widget_dims); |
| image.setImageDrawable(null); |
| name.setText(null); |
| dims.setText(null); |
| } |
| |
| public void deletePreview(boolean recycleImage) { |
| if (recycleImage) { |
| final ImageView image = (ImageView) findViewById(R.id.widget_preview); |
| if (image != null) { |
| image.setImageDrawable(null); |
| } |
| } |
| |
| if (mActiveRequest != null) { |
| mActiveRequest.cancel(recycleImage); |
| mActiveRequest = null; |
| } |
| } |
| |
| public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, |
| int maxWidth, WidgetPreviewLoader loader) { |
| LauncherAppState app = LauncherAppState.getInstance(); |
| DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); |
| |
| mIsAppWidget = true; |
| mInfo = info; |
| final ImageView image = (ImageView) findViewById(R.id.widget_preview); |
| if (maxWidth > -1) { |
| image.setMaxWidth(maxWidth); |
| } |
| final TextView name = (TextView) findViewById(R.id.widget_name); |
| name.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); |
| final TextView dims = (TextView) findViewById(R.id.widget_dims); |
| if (dims != null) { |
| int hSpan = Math.min(info.spanX, (int) grid.numColumns); |
| int vSpan = Math.min(info.spanY, (int) grid.numRows); |
| dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); |
| } |
| mWidgetPreviewLoader = loader; |
| } |
| |
| public void applyFromResolveInfo( |
| PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { |
| mIsAppWidget = false; |
| mInfo = info; |
| CharSequence label = info.loadLabel(pm); |
| final TextView name = (TextView) findViewById(R.id.widget_name); |
| name.setText(label); |
| final TextView dims = (TextView) findViewById(R.id.widget_dims); |
| if (dims != null) { |
| dims.setText(String.format(mDimensionsFormatString, 1, 1)); |
| } |
| mWidgetPreviewLoader = loader; |
| } |
| |
| public int[] getPreviewSize() { |
| final ImageView i = (ImageView) findViewById(R.id.widget_preview); |
| int[] maxSize = new int[2]; |
| maxSize[0] = mPresetPreviewSize; |
| maxSize[1] = mPresetPreviewSize; |
| return maxSize; |
| } |
| |
| public void applyPreview(Bitmap bitmap) { |
| FastBitmapDrawable preview = new FastBitmapDrawable(bitmap); |
| final WidgetImageView image = |
| (WidgetImageView) findViewById(R.id.widget_preview); |
| if (DEBUG) { |
| Log.d(TAG, String.format("[tag=%s] applyPreview preview: %s", |
| getTagToString(), preview)); |
| } |
| if (preview != null) { |
| image.mAllowRequestLayout = false; |
| image.setImageDrawable(preview); |
| if (mIsAppWidget) { |
| // center horizontally |
| int[] imageSize = getPreviewSize(); |
| int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2; |
| image.setPadding(mOriginalImagePadding.left + centerAmount, |
| mOriginalImagePadding.top, |
| mOriginalImagePadding.right, |
| mOriginalImagePadding.bottom); |
| } |
| image.setAlpha(1f); |
| image.mAllowRequestLayout = true; |
| image.requestLayout(); |
| } |
| } |
| |
| void setShortPressListener(ShortPressListener listener) { |
| mShortPressListener = listener; |
| } |
| |
| interface ShortPressListener { |
| void onShortPress(View v); |
| void cleanUpShortPress(View v); |
| } |
| |
| class CheckForShortPress implements Runnable { |
| public void run() { |
| if (sShortpressTarget != null) return; |
| if (mShortPressListener != null) { |
| mShortPressListener.onShortPress(WidgetCell.this); |
| sShortpressTarget = WidgetCell.this; |
| } |
| mShortPressTriggered = true; |
| } |
| } |
| |
| private void checkForShortPress() { |
| if (sShortpressTarget != null) return; |
| if (mPendingCheckForShortPress == null) { |
| mPendingCheckForShortPress = new CheckForShortPress(); |
| } |
| postDelayed(mPendingCheckForShortPress, 120); |
| } |
| |
| /** |
| * Remove the longpress detection timer. |
| */ |
| private void removeShortPressCallback() { |
| if (mPendingCheckForShortPress != null) { |
| removeCallbacks(mPendingCheckForShortPress); |
| } |
| } |
| |
| private void cleanUpShortPress() { |
| removeShortPressCallback(); |
| if (mShortPressTriggered) { |
| if (mShortPressListener != null) { |
| mShortPressListener.cleanUpShortPress(WidgetCell.this); |
| } |
| mShortPressTriggered = false; |
| } |
| } |
| |
| static void resetShortPressTarget() { |
| sShortpressTarget = null; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| super.onTouchEvent(event); |
| |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_UP: |
| cleanUpShortPress(); |
| break; |
| case MotionEvent.ACTION_DOWN: |
| checkForShortPress(); |
| break; |
| case MotionEvent.ACTION_CANCEL: |
| cleanUpShortPress(); |
| break; |
| case MotionEvent.ACTION_MOVE: |
| break; |
| } |
| |
| // We eat up the touch events here, since the PagedView (which uses the same swiping |
| // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when |
| // the user is scrolling between pages. This means that if the pages themselves don't |
| // handle touch events, it gets forwarded up to PagedView itself, and it's own |
| // onTouchEvent() handling will prevent further intercept touch events from being called |
| // (it's the same view in that case). This is not ideal, but to prevent more changes, |
| // we just always mark the touch event as handled. |
| return true; |
| } |
| |
| public void ensurePreview() { |
| if (mActiveRequest != null) { |
| return; |
| } |
| int[] size = getPreviewSize(); |
| if (DEBUG) { |
| Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):", |
| getTagToString(), size[0], size[1])); |
| } |
| |
| if (size[0] <= 0 || size[1] <= 0) { |
| addOnLayoutChangeListener(this); |
| return; |
| } |
| Bitmap[] immediateResult = new Bitmap[1]; |
| mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this, |
| immediateResult); |
| if (immediateResult[0] != null) { |
| applyPreview(immediateResult[0]); |
| } |
| } |
| |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, |
| int oldTop, int oldRight, int oldBottom) { |
| removeOnLayoutChangeListener(this); |
| ensurePreview(); |
| } |
| |
| public int getActualItemWidth() { |
| ItemInfo info = (ItemInfo) getTag(); |
| int[] size = getPreviewSize(); |
| int cellWidth = LauncherAppState.getInstance() |
| .getDynamicGrid().getDeviceProfile().cellWidthPx; |
| |
| return Math.min(size[0], info.spanX * cellWidth); |
| } |
| |
| /** |
| * Helper method to get the string info of the tag. |
| */ |
| private String getTagToString() { |
| if (getTag() instanceof PendingAddWidgetInfo) { |
| return ((PendingAddWidgetInfo)getTag()).toString(); |
| } else if (getTag() instanceof PendingAddShortcutInfo) { |
| return ((PendingAddShortcutInfo)getTag()).toString(); |
| } |
| return ""; |
| } |
| } |