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