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