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