1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.widget;
18 
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.os.CancellationSignal;
22 import android.util.AttributeSet;
23 import android.util.Log;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.view.View.OnLayoutChangeListener;
27 import android.view.ViewGroup;
28 import android.view.ViewPropertyAnimator;
29 import android.view.accessibility.AccessibilityNodeInfo;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.launcher3.BaseActivity;
34 import com.android.launcher3.CheckLongPressHelper;
35 import com.android.launcher3.DeviceProfile;
36 import com.android.launcher3.R;
37 import com.android.launcher3.WidgetPreviewLoader;
38 import com.android.launcher3.icons.BaseIconFactory;
39 import com.android.launcher3.model.WidgetItem;
40 
41 /**
42  * Represents the individual cell of the widget inside the widget tray. The preview is drawn
43  * horizontally centered, and scaled down if needed.
44  *
45  * This view does not support padding. Since the image is scaled down to fit the view, padding will
46  * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth
47  * transition from the view to drag view, so when adding padding support, DnD would need to
48  * consider the appropriate scaling factor.
49  */
50 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
51 
52     private static final String TAG = "WidgetCell";
53     private static final boolean DEBUG = false;
54 
55     private static final int FADE_IN_DURATION_MS = 90;
56 
57     /** Widget cell width is calculated by multiplying this factor to grid cell width. */
58     private static final float WIDTH_SCALE = 3f;
59 
60     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
61     private static final float PREVIEW_SCALE = 0.8f;
62 
63     protected int mPresetPreviewSize;
64     private int mCellSize;
65 
66     private WidgetImageView mWidgetImage;
67     private TextView mWidgetName;
68     private TextView mWidgetDims;
69 
70     protected WidgetItem mItem;
71 
72     private WidgetPreviewLoader mWidgetPreviewLoader;
73 
74     protected CancellationSignal mActiveRequest;
75     private boolean mAnimatePreview = true;
76 
77     private boolean mApplyBitmapDeferred = false;
78     private Bitmap mDeferredBitmap;
79 
80     protected final BaseActivity mActivity;
81     protected final DeviceProfile mDeviceProfile;
82     private final CheckLongPressHelper mLongPressHelper;
83 
WidgetCell(Context context)84     public WidgetCell(Context context) {
85         this(context, null);
86     }
87 
WidgetCell(Context context, AttributeSet attrs)88     public WidgetCell(Context context, AttributeSet attrs) {
89         this(context, attrs, 0);
90     }
91 
WidgetCell(Context context, AttributeSet attrs, int defStyle)92     public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
93         super(context, attrs, defStyle);
94 
95         mActivity = BaseActivity.fromContext(context);
96         mDeviceProfile = mActivity.getDeviceProfile();
97         mLongPressHelper = new CheckLongPressHelper(this);
98 
99         mLongPressHelper.setLongPressTimeoutFactor(1);
100         setContainerWidth();
101         setWillNotDraw(false);
102         setClipToPadding(false);
103         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
104     }
105 
setContainerWidth()106     private void setContainerWidth() {
107         mCellSize = (int) (mDeviceProfile.allAppsIconSizePx * WIDTH_SCALE);
108         mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
109     }
110 
111     @Override
onFinishInflate()112     protected void onFinishInflate() {
113         super.onFinishInflate();
114 
115         mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview);
116         mWidgetName = ((TextView) findViewById(R.id.widget_name));
117         mWidgetDims = ((TextView) findViewById(R.id.widget_dims));
118     }
119 
120     /**
121      * Called to clear the view and free attached resources. (e.g., {@link Bitmap}
122      */
clear()123     public void clear() {
124         if (DEBUG) {
125             Log.d(TAG, "reset called on:" + mWidgetName.getText());
126         }
127         mWidgetImage.animate().cancel();
128         mWidgetImage.setBitmap(null, null);
129         mWidgetName.setText(null);
130         mWidgetDims.setText(null);
131 
132         if (mActiveRequest != null) {
133             mActiveRequest.cancel();
134             mActiveRequest = null;
135         }
136     }
137 
applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader)138     public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
139         mItem = item;
140         mWidgetName.setText(mItem.label);
141         mWidgetDims.setText(getContext().getString(R.string.widget_dims_format,
142                 mItem.spanX, mItem.spanY));
143         mWidgetDims.setContentDescription(getContext().getString(
144                 R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
145         mWidgetPreviewLoader = loader;
146 
147         if (item.activityInfo != null) {
148             setTag(new PendingAddShortcutInfo(item.activityInfo));
149         } else {
150             setTag(new PendingAddWidgetInfo(item.widgetInfo));
151         }
152     }
153 
getWidgetView()154     public WidgetImageView getWidgetView() {
155         return mWidgetImage;
156     }
157 
158     /**
159      * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
160      * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
161      * ready.
162      * This prevents invalidates while the animation is running.
163      */
setApplyBitmapDeferred(boolean isDeferred)164     public void setApplyBitmapDeferred(boolean isDeferred) {
165         if (mApplyBitmapDeferred != isDeferred) {
166             mApplyBitmapDeferred = isDeferred;
167             if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
168                 applyPreview(mDeferredBitmap);
169                 mDeferredBitmap = null;
170             }
171         }
172     }
173 
setAnimatePreview(boolean shouldAnimate)174     public void setAnimatePreview(boolean shouldAnimate) {
175         mAnimatePreview = shouldAnimate;
176     }
177 
applyPreview(Bitmap bitmap)178     public void applyPreview(Bitmap bitmap) {
179         if (mApplyBitmapDeferred) {
180             mDeferredBitmap = bitmap;
181             return;
182         }
183         if (bitmap != null) {
184             mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
185                     BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
186             if (mAnimatePreview) {
187                 mWidgetImage.setAlpha(0f);
188                 ViewPropertyAnimator anim = mWidgetImage.animate();
189                 anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
190             } else {
191                 mWidgetImage.setAlpha(1f);
192             }
193         }
194     }
195 
ensurePreview()196     public void ensurePreview() {
197         if (mActiveRequest != null) {
198             return;
199         }
200         mActiveRequest = mWidgetPreviewLoader.getPreview(
201                 mItem, mPresetPreviewSize, mPresetPreviewSize, this);
202     }
203 
204     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)205     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
206             int oldTop, int oldRight, int oldBottom) {
207         removeOnLayoutChangeListener(this);
208         ensurePreview();
209     }
210 
211     @Override
onTouchEvent(MotionEvent ev)212     public boolean onTouchEvent(MotionEvent ev) {
213         super.onTouchEvent(ev);
214         mLongPressHelper.onTouchEvent(ev);
215         return true;
216     }
217 
218     @Override
cancelLongPress()219     public void cancelLongPress() {
220         super.cancelLongPress();
221         mLongPressHelper.cancelLongPress();
222     }
223 
224     /**
225      * Helper method to get the string info of the tag.
226      */
getTagToString()227     private String getTagToString() {
228         if (getTag() instanceof PendingAddWidgetInfo ||
229                 getTag() instanceof PendingAddShortcutInfo) {
230             return getTag().toString();
231         }
232         return "";
233     }
234 
235     @Override
setLayoutParams(ViewGroup.LayoutParams params)236     public void setLayoutParams(ViewGroup.LayoutParams params) {
237         params.width = params.height = mCellSize;
238         super.setLayoutParams(params);
239     }
240 
241     @Override
getAccessibilityClassName()242     public CharSequence getAccessibilityClassName() {
243         return WidgetCell.class.getName();
244     }
245 
246     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)247     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
248         super.onInitializeAccessibilityNodeInfo(info);
249         info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
250     }
251 }
252