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.content.pm.PackageManager;
21 import android.content.pm.ResolveInfo;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.View.OnLayoutChangeListener;
29 import android.view.ViewPropertyAnimator;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.InvariantDeviceProfile;
35 import com.android.launcher3.ItemInfo;
36 import com.android.launcher3.Launcher;
37 import com.android.launcher3.LauncherAppState;
38 import com.android.launcher3.LauncherAppWidgetProviderInfo;
39 import com.android.launcher3.R;
40 import com.android.launcher3.StylusEventHelper;
41 import com.android.launcher3.WidgetPreviewLoader;
42 import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
43 import com.android.launcher3.compat.AppWidgetManagerCompat;
44 
45 /**
46  * Represents the individual cell of the widget inside the widget tray. The preview is drawn
47  * horizontally centered, and scaled down if needed.
48  *
49  * This view does not support padding. Since the image is scaled down to fit the view, padding will
50  * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth
51  * transition from the view to drag view, so when adding padding support, DnD would need to
52  * consider the appropriate scaling factor.
53  */
54 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
55 
56     private static final String TAG = "WidgetCell";
57     private static final boolean DEBUG = false;
58 
59     private static final int FADE_IN_DURATION_MS = 90;
60 
61     /** Widget cell width is calculated by multiplying this factor to grid cell width. */
62     private static final float WIDTH_SCALE = 2.6f;
63 
64     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
65     private static final float PREVIEW_SCALE = 0.8f;
66 
67     private int mPresetPreviewSize;
68     int cellSize;
69 
70     private WidgetImageView mWidgetImage;
71     private TextView mWidgetName;
72     private TextView mWidgetDims;
73 
74     private String mDimensionsFormatString;
75     private Object mInfo;
76 
77     private WidgetPreviewLoader mWidgetPreviewLoader;
78     private PreviewLoadRequest mActiveRequest;
79     private StylusEventHelper mStylusEventHelper;
80 
81     private Launcher mLauncher;
82 
WidgetCell(Context context)83     public WidgetCell(Context context) {
84         this(context, null);
85     }
86 
WidgetCell(Context context, AttributeSet attrs)87     public WidgetCell(Context context, AttributeSet attrs) {
88         this(context, attrs, 0);
89     }
90 
WidgetCell(Context context, AttributeSet attrs, int defStyle)91     public WidgetCell(Context context, AttributeSet attrs, int defStyle) {
92         super(context, attrs, defStyle);
93 
94         final Resources r = context.getResources();
95         mLauncher = (Launcher) context;
96         mStylusEventHelper = new StylusEventHelper(this);
97 
98         mDimensionsFormatString = r.getString(R.string.widget_dims_format);
99         setContainerWidth();
100         setWillNotDraw(false);
101         setClipToPadding(false);
102         setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate());
103     }
104 
setContainerWidth()105     private void setContainerWidth() {
106         DeviceProfile profile = mLauncher.getDeviceProfile();
107         cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
108         mPresetPreviewSize = (int) (cellSize * 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);
129         mWidgetName.setText(null);
130         mWidgetDims.setText(null);
131 
132         if (mActiveRequest != null) {
133             mActiveRequest.cleanup();
134             mActiveRequest = null;
135         }
136     }
137 
138     /**
139      * Apply the widget provider info to the view.
140      */
applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, WidgetPreviewLoader loader)141     public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
142             WidgetPreviewLoader loader) {
143 
144         InvariantDeviceProfile profile =
145                 LauncherAppState.getInstance().getInvariantDeviceProfile();
146         mInfo = info;
147         // TODO(hyunyoungs): setup a cache for these labels.
148         mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
149         int hSpan = Math.min(info.spanX, profile.numColumns);
150         int vSpan = Math.min(info.spanY, profile.numRows);
151         mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
152         mWidgetPreviewLoader = loader;
153     }
154 
155     /**
156      * Apply the resolve info to the view.
157      */
applyFromResolveInfo( PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader)158     public void applyFromResolveInfo(
159             PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
160         mInfo = info;
161         CharSequence label = info.loadLabel(pm);
162         mWidgetName.setText(label);
163         mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1));
164         mWidgetPreviewLoader = loader;
165     }
166 
getPreviewSize()167     public int[] getPreviewSize() {
168         int[] maxSize = new int[2];
169 
170         maxSize[0] = mPresetPreviewSize;
171         maxSize[1] = mPresetPreviewSize;
172         return maxSize;
173     }
174 
applyPreview(Bitmap bitmap)175     public void applyPreview(Bitmap bitmap) {
176         if (bitmap != null) {
177             mWidgetImage.setBitmap(bitmap);
178             mWidgetImage.setAlpha(0f);
179             ViewPropertyAnimator anim = mWidgetImage.animate();
180             anim.alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
181         }
182     }
183 
ensurePreview()184     public void ensurePreview() {
185         if (mActiveRequest != null) {
186             return;
187         }
188         int[] size = getPreviewSize();
189         if (DEBUG) {
190             Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):",
191                     getTagToString(), size[0], size[1]));
192         }
193         mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this);
194     }
195 
196     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)197     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
198             int oldTop, int oldRight, int oldBottom) {
199         removeOnLayoutChangeListener(this);
200         ensurePreview();
201     }
202 
getActualItemWidth()203     public int getActualItemWidth() {
204         ItemInfo info = (ItemInfo) getTag();
205         int[] size = getPreviewSize();
206         int cellWidth = mLauncher.getDeviceProfile().cellWidthPx;
207 
208         return Math.min(size[0], info.spanX * cellWidth);
209     }
210 
211     @Override
onTouchEvent(MotionEvent ev)212     public boolean onTouchEvent(MotionEvent ev) {
213         boolean handled = super.onTouchEvent(ev);
214         if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
215             return true;
216         }
217         return handled;
218     }
219 
220     /**
221      * Helper method to get the string info of the tag.
222      */
getTagToString()223     private String getTagToString() {
224         if (getTag() instanceof PendingAddWidgetInfo ||
225                 getTag() instanceof PendingAddShortcutInfo) {
226             return getTag().toString();
227         }
228         return "";
229     }
230 }
231