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