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