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
17package com.android.launcher3.widget;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.MotionEvent;
27import android.view.View;
28import android.view.View.OnLayoutChangeListener;
29import android.view.ViewPropertyAnimator;
30import android.widget.LinearLayout;
31import android.widget.TextView;
32
33import com.android.launcher3.DeviceProfile;
34import com.android.launcher3.InvariantDeviceProfile;
35import com.android.launcher3.ItemInfo;
36import com.android.launcher3.Launcher;
37import com.android.launcher3.LauncherAppState;
38import com.android.launcher3.LauncherAppWidgetProviderInfo;
39import com.android.launcher3.R;
40import com.android.launcher3.StylusEventHelper;
41import com.android.launcher3.WidgetPreviewLoader;
42import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
43import 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 */
54public 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
83    public WidgetCell(Context context) {
84        this(context, null);
85    }
86
87    public WidgetCell(Context context, AttributeSet attrs) {
88        this(context, attrs, 0);
89    }
90
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
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
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     */
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     */
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     */
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
167    public int[] getPreviewSize() {
168        int[] maxSize = new int[2];
169
170        maxSize[0] = mPresetPreviewSize;
171        maxSize[1] = mPresetPreviewSize;
172        return maxSize;
173    }
174
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
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
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
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
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     */
223    private String getTagToString() {
224        if (getTag() instanceof PendingAddWidgetInfo ||
225                getTag() instanceof PendingAddShortcutInfo) {
226            return getTag().toString();
227        }
228        return "";
229    }
230}
231