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