Home | History | Annotate | Download | only in phone
      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 
     17 package com.android.systemui.statusbar.phone;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.app.StatusBarManager;
     23 import android.content.Context;
     24 import android.content.res.Resources;
     25 import android.graphics.Rect;
     26 import android.graphics.RectF;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.Handler;
     29 import android.os.Message;
     30 import android.os.ServiceManager;
     31 import android.util.AttributeSet;
     32 import android.util.Slog;
     33 import android.view.animation.AccelerateInterpolator;
     34 import android.view.Display;
     35 import android.view.MotionEvent;
     36 import android.view.VelocityTracker;
     37 import android.view.View;
     38 import android.view.ViewGroup;
     39 import android.view.Surface;
     40 import android.view.Window;
     41 import android.view.WindowManager;
     42 import android.view.WindowManagerImpl;
     43 import android.widget.ImageView;
     44 import android.widget.LinearLayout;
     45 
     46 import java.io.FileDescriptor;
     47 import java.io.PrintWriter;
     48 import java.lang.StringBuilder;
     49 
     50 import com.android.internal.statusbar.IStatusBarService;
     51 import com.android.systemui.R;
     52 import com.android.systemui.statusbar.BaseStatusBar;
     53 import com.android.systemui.statusbar.DelegateViewHelper;
     54 
     55 public 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