1 /* 2 * Copyright (C) 2009 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; 18 19 import android.appwidget.AppWidgetHostView; 20 import android.appwidget.AppWidgetProviderInfo; 21 import android.content.Context; 22 import android.graphics.Rect; 23 import android.view.KeyEvent; 24 import android.view.LayoutInflater; 25 import android.view.MotionEvent; 26 import android.view.View; 27 import android.view.ViewConfiguration; 28 import android.view.ViewGroup; 29 import android.widget.RemoteViews; 30 31 import com.android.launcher3.DragLayer.TouchCompleteListener; 32 33 import java.util.ArrayList; 34 35 /** 36 * {@inheritDoc} 37 */ 38 public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener { 39 40 LayoutInflater mInflater; 41 42 private CheckLongPressHelper mLongPressHelper; 43 private StylusEventHelper mStylusEventHelper; 44 private Context mContext; 45 private int mPreviousOrientation; 46 private DragLayer mDragLayer; 47 48 private float mSlop; 49 50 private boolean mChildrenFocused; 51 LauncherAppWidgetHostView(Context context)52 public LauncherAppWidgetHostView(Context context) { 53 super(context); 54 mContext = context; 55 mLongPressHelper = new CheckLongPressHelper(this); 56 mStylusEventHelper = new StylusEventHelper(this); 57 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 58 mDragLayer = ((Launcher) context).getDragLayer(); 59 setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); 60 61 setBackgroundResource(R.drawable.widget_internal_focus_bg); 62 } 63 64 @Override getErrorView()65 protected View getErrorView() { 66 return mInflater.inflate(R.layout.appwidget_error, this, false); 67 } 68 updateLastInflationOrientation()69 public void updateLastInflationOrientation() { 70 mPreviousOrientation = mContext.getResources().getConfiguration().orientation; 71 } 72 73 @Override updateAppWidget(RemoteViews remoteViews)74 public void updateAppWidget(RemoteViews remoteViews) { 75 // Store the orientation in which the widget was inflated 76 updateLastInflationOrientation(); 77 super.updateAppWidget(remoteViews); 78 } 79 isReinflateRequired()80 public boolean isReinflateRequired() { 81 // Re-inflate is required if the orientation has changed since last inflated. 82 int orientation = mContext.getResources().getConfiguration().orientation; 83 if (mPreviousOrientation != orientation) { 84 return true; 85 } 86 return false; 87 } 88 onInterceptTouchEvent(MotionEvent ev)89 public boolean onInterceptTouchEvent(MotionEvent ev) { 90 // Just in case the previous long press hasn't been cleared, we make sure to start fresh 91 // on touch down. 92 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 93 mLongPressHelper.cancelLongPress(); 94 } 95 96 // Consume any touch events for ourselves after longpress is triggered 97 if (mLongPressHelper.hasPerformedLongPress()) { 98 mLongPressHelper.cancelLongPress(); 99 return true; 100 } 101 102 // Watch for longpress or stylus button press events at this level to 103 // make sure users can always pick up this widget 104 if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { 105 mLongPressHelper.cancelLongPress(); 106 return true; 107 } 108 switch (ev.getAction()) { 109 case MotionEvent.ACTION_DOWN: { 110 if (!mStylusEventHelper.inStylusButtonPressed()) { 111 mLongPressHelper.postCheckForLongPress(); 112 } 113 mDragLayer.setTouchCompleteListener(this); 114 break; 115 } 116 117 case MotionEvent.ACTION_UP: 118 case MotionEvent.ACTION_CANCEL: 119 mLongPressHelper.cancelLongPress(); 120 break; 121 case MotionEvent.ACTION_MOVE: 122 if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) { 123 mLongPressHelper.cancelLongPress(); 124 } 125 break; 126 } 127 128 // Otherwise continue letting touch events fall through to children 129 return false; 130 } 131 onTouchEvent(MotionEvent ev)132 public boolean onTouchEvent(MotionEvent ev) { 133 // If the widget does not handle touch, then cancel 134 // long press when we release the touch 135 switch (ev.getAction()) { 136 case MotionEvent.ACTION_UP: 137 case MotionEvent.ACTION_CANCEL: 138 mLongPressHelper.cancelLongPress(); 139 break; 140 case MotionEvent.ACTION_MOVE: 141 if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) { 142 mLongPressHelper.cancelLongPress(); 143 } 144 break; 145 } 146 return false; 147 } 148 149 @Override onAttachedToWindow()150 protected void onAttachedToWindow() { 151 super.onAttachedToWindow(); 152 mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 153 } 154 155 @Override cancelLongPress()156 public void cancelLongPress() { 157 super.cancelLongPress(); 158 mLongPressHelper.cancelLongPress(); 159 } 160 161 @Override getAppWidgetInfo()162 public AppWidgetProviderInfo getAppWidgetInfo() { 163 AppWidgetProviderInfo info = super.getAppWidgetInfo(); 164 if (info != null && !(info instanceof LauncherAppWidgetProviderInfo)) { 165 throw new IllegalStateException("Launcher widget must have" 166 + " LauncherAppWidgetProviderInfo"); 167 } 168 return info; 169 } 170 getLauncherAppWidgetProviderInfo()171 public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() { 172 return (LauncherAppWidgetProviderInfo) getAppWidgetInfo(); 173 } 174 175 @Override onTouchComplete()176 public void onTouchComplete() { 177 if (!mLongPressHelper.hasPerformedLongPress()) { 178 // If a long press has been performed, we don't want to clear the record of that since 179 // we still may be receiving a touch up which we want to intercept 180 mLongPressHelper.cancelLongPress(); 181 } 182 } 183 184 @Override getDescendantFocusability()185 public int getDescendantFocusability() { 186 return mChildrenFocused ? ViewGroup.FOCUS_BEFORE_DESCENDANTS 187 : ViewGroup.FOCUS_BLOCK_DESCENDANTS; 188 } 189 190 @Override dispatchKeyEvent(KeyEvent event)191 public boolean dispatchKeyEvent(KeyEvent event) { 192 if (mChildrenFocused && event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE 193 && event.getAction() == KeyEvent.ACTION_UP) { 194 mChildrenFocused = false; 195 requestFocus(); 196 return true; 197 } 198 return super.dispatchKeyEvent(event); 199 } 200 201 @Override onKeyDown(int keyCode, KeyEvent event)202 public boolean onKeyDown(int keyCode, KeyEvent event) { 203 if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) { 204 event.startTracking(); 205 return true; 206 } 207 return super.onKeyDown(keyCode, event); 208 } 209 210 @Override onKeyUp(int keyCode, KeyEvent event)211 public boolean onKeyUp(int keyCode, KeyEvent event) { 212 if (event.isTracking()) { 213 if (!mChildrenFocused && keyCode == KeyEvent.KEYCODE_ENTER) { 214 mChildrenFocused = true; 215 ArrayList<View> focusableChildren = getFocusables(FOCUS_FORWARD); 216 focusableChildren.remove(this); 217 int childrenCount = focusableChildren.size(); 218 switch (childrenCount) { 219 case 0: 220 mChildrenFocused = false; 221 break; 222 case 1: { 223 if (getTag() instanceof ItemInfo) { 224 ItemInfo item = (ItemInfo) getTag(); 225 if (item.spanX == 1 && item.spanY == 1) { 226 focusableChildren.get(0).performClick(); 227 mChildrenFocused = false; 228 return true; 229 } 230 } 231 // continue; 232 } 233 default: 234 focusableChildren.get(0).requestFocus(); 235 return true; 236 } 237 } 238 } 239 return super.onKeyUp(keyCode, event); 240 } 241 242 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)243 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 244 if (gainFocus) { 245 mChildrenFocused = false; 246 dispatchChildFocus(false); 247 } 248 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 249 } 250 251 @Override requestChildFocus(View child, View focused)252 public void requestChildFocus(View child, View focused) { 253 super.requestChildFocus(child, focused); 254 dispatchChildFocus(mChildrenFocused && focused != null); 255 if (focused != null) { 256 focused.setFocusableInTouchMode(false); 257 } 258 } 259 260 @Override clearChildFocus(View child)261 public void clearChildFocus(View child) { 262 super.clearChildFocus(child); 263 dispatchChildFocus(false); 264 } 265 266 @Override dispatchUnhandledMove(View focused, int direction)267 public boolean dispatchUnhandledMove(View focused, int direction) { 268 return mChildrenFocused; 269 } 270 dispatchChildFocus(boolean childIsFocused)271 private void dispatchChildFocus(boolean childIsFocused) { 272 // The host view's background changes when selected, to indicate the focus is inside. 273 setSelected(childIsFocused); 274 } 275 276 @Override onLayout(boolean changed, int left, int top, int right, int bottom)277 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 278 try { 279 super.onLayout(changed, left, top, right, bottom); 280 } catch (final RuntimeException e) { 281 post(new Runnable() { 282 @Override 283 public void run() { 284 // Update the widget with 0 Layout id, to reset the view to error view. 285 updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0)); 286 } 287 }); 288 } 289 } 290 } 291