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