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