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