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