1/*
2 * Copyright (C) 2008 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.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.app.StatusBarManager;
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.graphics.drawable.Drawable;
28import android.os.Handler;
29import android.os.Message;
30import android.os.ServiceManager;
31import android.util.AttributeSet;
32import android.util.Slog;
33import android.view.animation.AccelerateInterpolator;
34import android.view.Display;
35import android.view.MotionEvent;
36import android.view.VelocityTracker;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.Surface;
40import android.view.Window;
41import android.view.WindowManager;
42import android.view.WindowManagerImpl;
43import android.widget.ImageView;
44import android.widget.LinearLayout;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.lang.StringBuilder;
49
50import com.android.internal.statusbar.IStatusBarService;
51import com.android.systemui.R;
52import com.android.systemui.statusbar.BaseStatusBar;
53import com.android.systemui.statusbar.DelegateViewHelper;
54
55public class NavigationBarView extends LinearLayout {
56    final static boolean DEBUG = false;
57    final static String TAG = "PhoneStatusBar/NavigationBarView";
58
59    final static boolean DEBUG_DEADZONE = false;
60
61    final static boolean NAVBAR_ALWAYS_AT_RIGHT = true;
62
63    final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen
64
65    protected IStatusBarService mBarService;
66    final Display mDisplay;
67    View mCurrentView = null;
68    View[] mRotatedViews = new View[4];
69
70    int mBarSize;
71    boolean mVertical;
72
73    boolean mHidden, mLowProfile, mShowMenu;
74    int mDisabledFlags = 0;
75    int mNavigationIconHints = 0;
76
77    private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
78
79    private DelegateViewHelper mDelegateHelper;
80
81    // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
82    final static boolean WORKAROUND_INVALID_LAYOUT = true;
83    final static int MSG_CHECK_INVALID_LAYOUT = 8686;
84
85    private class H extends Handler {
86        public void handleMessage(Message m) {
87            switch (m.what) {
88                case MSG_CHECK_INVALID_LAYOUT:
89                    final String how = "" + m.obj;
90                    final int w = getWidth();
91                    final int h = getHeight();
92                    final int vw = mCurrentView.getWidth();
93                    final int vh = mCurrentView.getHeight();
94
95                    if (h != vh || w != vw) {
96                        Slog.w(TAG, String.format(
97                            "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
98                            how, w, h, vw, vh));
99                        if (WORKAROUND_INVALID_LAYOUT) {
100                            requestLayout();
101                        }
102                    }
103                    break;
104            }
105        }
106    }
107
108    public void setDelegateView(View view) {
109        mDelegateHelper.setDelegateView(view);
110    }
111
112    public void setBar(BaseStatusBar phoneStatusBar) {
113        mDelegateHelper.setBar(phoneStatusBar);
114    }
115
116    @Override
117    public boolean onTouchEvent(MotionEvent event) {
118        if (mDelegateHelper != null) {
119            mDelegateHelper.onInterceptTouchEvent(event);
120        }
121        return true;
122    }
123
124    @Override
125    public boolean onInterceptTouchEvent(MotionEvent event) {
126        return mDelegateHelper.onInterceptTouchEvent(event);
127    }
128
129    private H mHandler = new H();
130
131    public View getRecentsButton() {
132        return mCurrentView.findViewById(R.id.recent_apps);
133    }
134
135    public View getMenuButton() {
136        return mCurrentView.findViewById(R.id.menu);
137    }
138
139    public View getBackButton() {
140        return mCurrentView.findViewById(R.id.back);
141    }
142
143    public View getHomeButton() {
144        return mCurrentView.findViewById(R.id.home);
145    }
146
147    public NavigationBarView(Context context, AttributeSet attrs) {
148        super(context, attrs);
149
150        mHidden = false;
151
152        mDisplay = ((WindowManager)context.getSystemService(
153                Context.WINDOW_SERVICE)).getDefaultDisplay();
154        mBarService = IStatusBarService.Stub.asInterface(
155                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
156
157        final Resources res = mContext.getResources();
158        mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
159        mVertical = false;
160        mShowMenu = false;
161        mDelegateHelper = new DelegateViewHelper(this);
162
163        mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
164        mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land);
165        mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
166        mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
167    }
168
169    View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
170        @Override
171        public boolean onTouch(View v, MotionEvent ev) {
172            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
173                // even though setting the systemUI visibility below will turn these views
174                // on, we need them to come up faster so that they can catch this motion
175                // event
176                setLowProfile(false, false, false);
177
178                try {
179                    mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE);
180                } catch (android.os.RemoteException ex) {
181                }
182            }
183            return false;
184        }
185    };
186
187    public void setNavigationIconHints(int hints) {
188        setNavigationIconHints(hints, false);
189    }
190
191    public void setNavigationIconHints(int hints, boolean force) {
192        if (!force && hints == mNavigationIconHints) return;
193
194        if (DEBUG) {
195            android.widget.Toast.makeText(mContext,
196                "Navigation icon hints = " + hints,
197                500).show();
198        }
199
200        mNavigationIconHints = hints;
201
202        getBackButton().setAlpha(
203            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f);
204        getHomeButton().setAlpha(
205            (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f);
206        getRecentsButton().setAlpha(
207            (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f);
208
209        ((ImageView)getBackButton()).setImageDrawable(
210            (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT))
211                ? (mVertical ? mBackAltLandIcon : mBackAltIcon)
212                : (mVertical ? mBackLandIcon : mBackIcon));
213    }
214
215    public void setDisabledFlags(int disabledFlags) {
216        setDisabledFlags(disabledFlags, false);
217    }
218
219    public void setDisabledFlags(int disabledFlags, boolean force) {
220        if (!force && mDisabledFlags == disabledFlags) return;
221
222        mDisabledFlags = disabledFlags;
223
224        final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
225        final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
226        final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0);
227
228        setSlippery(disableHome && disableRecent && disableBack);
229
230        getBackButton()   .setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
231        getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
232        getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
233    }
234
235    public void setSlippery(boolean newSlippery) {
236        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
237        if (lp != null) {
238            boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0;
239            if (!oldSlippery && newSlippery) {
240                lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
241            } else if (oldSlippery && !newSlippery) {
242                lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
243            } else {
244                return;
245            }
246            WindowManagerImpl.getDefault().updateViewLayout(this, lp);
247        }
248    }
249
250    public void setMenuVisibility(final boolean show) {
251        setMenuVisibility(show, false);
252    }
253
254    public void setMenuVisibility(final boolean show, final boolean force) {
255        if (!force && mShowMenu == show) return;
256
257        mShowMenu = show;
258
259        getMenuButton().setVisibility(mShowMenu ? View.VISIBLE : View.INVISIBLE);
260    }
261
262    public void setLowProfile(final boolean lightsOut) {
263        setLowProfile(lightsOut, true, false);
264    }
265
266    public void setLowProfile(final boolean lightsOut, final boolean animate, final boolean force) {
267        if (!force && lightsOut == mLowProfile) return;
268
269        mLowProfile = lightsOut;
270
271        if (DEBUG) Slog.d(TAG, "setting lights " + (lightsOut?"out":"on"));
272
273        final View navButtons = mCurrentView.findViewById(R.id.nav_buttons);
274        final View lowLights = mCurrentView.findViewById(R.id.lights_out);
275
276        // ok, everyone, stop it right there
277        navButtons.animate().cancel();
278        lowLights.animate().cancel();
279
280        if (!animate) {
281            navButtons.setAlpha(lightsOut ? 0f : 1f);
282
283            lowLights.setAlpha(lightsOut ? 1f : 0f);
284            lowLights.setVisibility(lightsOut ? View.VISIBLE : View.GONE);
285        } else {
286            navButtons.animate()
287                .alpha(lightsOut ? 0f : 1f)
288                .setDuration(lightsOut ? 750 : 250)
289                .start();
290
291            lowLights.setOnTouchListener(mLightsOutListener);
292            if (lowLights.getVisibility() == View.GONE) {
293                lowLights.setAlpha(0f);
294                lowLights.setVisibility(View.VISIBLE);
295            }
296            lowLights.animate()
297                .alpha(lightsOut ? 1f : 0f)
298                .setDuration(lightsOut ? 750 : 250)
299                .setInterpolator(new AccelerateInterpolator(2.0f))
300                .setListener(lightsOut ? null : new AnimatorListenerAdapter() {
301                    @Override
302                    public void onAnimationEnd(Animator _a) {
303                        lowLights.setVisibility(View.GONE);
304                    }
305                })
306                .start();
307        }
308    }
309
310    public void setHidden(final boolean hide) {
311        if (hide == mHidden) return;
312
313        mHidden = hide;
314        Slog.d(TAG,
315            (hide ? "HIDING" : "SHOWING") + " navigation bar");
316
317        // bring up the lights no matter what
318        setLowProfile(false);
319    }
320
321    @Override
322    public void onFinishInflate() {
323        mRotatedViews[Surface.ROTATION_0] =
324        mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
325
326        mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
327
328        mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT
329                                                ? findViewById(R.id.rot90)
330                                                : findViewById(R.id.rot270);
331
332        mCurrentView = mRotatedViews[Surface.ROTATION_0];
333    }
334
335    public void reorient() {
336        final int rot = mDisplay.getRotation();
337        for (int i=0; i<4; i++) {
338            mRotatedViews[i].setVisibility(View.GONE);
339        }
340        mCurrentView = mRotatedViews[rot];
341        mCurrentView.setVisibility(View.VISIBLE);
342
343        // force the low profile & disabled states into compliance
344        setLowProfile(mLowProfile, false, true /* force */);
345        setDisabledFlags(mDisabledFlags, true /* force */);
346        setMenuVisibility(mShowMenu, true /* force */);
347
348        if (DEBUG_DEADZONE) {
349            mCurrentView.findViewById(R.id.deadzone).setBackgroundColor(0x808080FF);
350        }
351
352        if (DEBUG) {
353            Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
354        }
355
356        setNavigationIconHints(mNavigationIconHints, true);
357    }
358
359    @Override
360    protected void onLayout(boolean changed, int l, int t, int r, int b) {
361        super.onLayout(changed, l, t, r, b);
362        mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton());
363    }
364
365    @Override
366    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
367        if (DEBUG) Slog.d(TAG, String.format(
368                    "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
369
370        final boolean newVertical = w > 0 && h > w;
371        if (newVertical != mVertical) {
372            mVertical = newVertical;
373            //Slog.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
374            reorient();
375        }
376
377        postCheckForInvalidLayout("sizeChanged");
378        super.onSizeChanged(w, h, oldw, oldh);
379    }
380
381    /*
382    @Override
383    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
384        if (DEBUG) Slog.d(TAG, String.format(
385                    "onLayout: %s (%d,%d,%d,%d)",
386                    changed?"changed":"notchanged", left, top, right, bottom));
387        super.onLayout(changed, left, top, right, bottom);
388    }
389
390    // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
391    // fails, any touch on the display will fix the layout.
392    @Override
393    public boolean onInterceptTouchEvent(MotionEvent ev) {
394        if (DEBUG) Slog.d(TAG, "onInterceptTouchEvent: " + ev.toString());
395        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
396            postCheckForInvalidLayout("touch");
397        }
398        return super.onInterceptTouchEvent(ev);
399    }
400    */
401
402
403    private String getResourceName(int resId) {
404        if (resId != 0) {
405            final android.content.res.Resources res = mContext.getResources();
406            try {
407                return res.getResourceName(resId);
408            } catch (android.content.res.Resources.NotFoundException ex) {
409                return "(unknown)";
410            }
411        } else {
412            return "(null)";
413        }
414    }
415
416    private void postCheckForInvalidLayout(final String how) {
417        mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
418    }
419
420    private static String visibilityToString(int vis) {
421        switch (vis) {
422            case View.INVISIBLE:
423                return "INVISIBLE";
424            case View.GONE:
425                return "GONE";
426            default:
427                return "VISIBLE";
428        }
429    }
430
431    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
432        pw.println("NavigationBarView {");
433        final Rect r = new Rect();
434
435        pw.println(String.format("      this: " + PhoneStatusBar.viewInfo(this)
436                        + " " + visibilityToString(getVisibility())));
437
438        getWindowVisibleDisplayFrame(r);
439        final boolean offscreen = r.right > mDisplay.getRawWidth()
440            || r.bottom > mDisplay.getRawHeight();
441        pw.println("      window: "
442                + r.toShortString()
443                + " " + visibilityToString(getWindowVisibility())
444                + (offscreen ? " OFFSCREEN!" : ""));
445
446        pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
447                        getResourceName(mCurrentView.getId()),
448                        mCurrentView.getWidth(), mCurrentView.getHeight(),
449                        visibilityToString(mCurrentView.getVisibility())));
450
451        pw.println(String.format("      disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s",
452                        mDisabledFlags,
453                        mVertical ? "true" : "false",
454                        mHidden ? "true" : "false",
455                        mLowProfile ? "true" : "false",
456                        mShowMenu ? "true" : "false"));
457
458        final View back = getBackButton();
459        final View home = getHomeButton();
460        final View recent = getRecentsButton();
461        final View menu = getMenuButton();
462
463        pw.println("      back: "
464                + PhoneStatusBar.viewInfo(back)
465                + " " + visibilityToString(back.getVisibility())
466                );
467        pw.println("      home: "
468                + PhoneStatusBar.viewInfo(home)
469                + " " + visibilityToString(home.getVisibility())
470                );
471        pw.println("      rcnt: "
472                + PhoneStatusBar.viewInfo(recent)
473                + " " + visibilityToString(recent.getVisibility())
474                );
475        pw.println("      menu: "
476                + PhoneStatusBar.viewInfo(menu)
477                + " " + visibilityToString(menu.getVisibility())
478                );
479        pw.println("    }");
480    }
481
482}
483