| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.statusbar.phone; |
| |
| import android.animation.LayoutTransition; |
| import android.animation.LayoutTransition.TransitionListener; |
| import android.animation.ObjectAnimator; |
| import android.animation.TimeInterpolator; |
| import android.animation.ValueAnimator; |
| import android.app.StatusBarManager; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.Display; |
| import android.view.MotionEvent; |
| import android.view.Surface; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.statusbar.BaseStatusBar; |
| import com.android.systemui.statusbar.DelegateViewHelper; |
| import com.android.systemui.statusbar.policy.DeadZone; |
| import com.android.systemui.statusbar.policy.KeyButtonView; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| |
| public class NavigationBarView extends LinearLayout { |
| final static boolean DEBUG = false; |
| final static String TAG = "PhoneStatusBar/NavigationBarView"; |
| |
| final static boolean NAVBAR_ALWAYS_AT_RIGHT = true; |
| |
| // slippery nav bar when everything is disabled, e.g. during setup |
| final static boolean SLIPPERY_WHEN_DISABLED = true; |
| |
| final Display mDisplay; |
| View mCurrentView = null; |
| View[] mRotatedViews = new View[4]; |
| |
| int mBarSize; |
| boolean mVertical; |
| boolean mScreenOn; |
| |
| boolean mShowMenu; |
| int mDisabledFlags = 0; |
| int mNavigationIconHints = 0; |
| |
| private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; |
| private Drawable mRecentIcon; |
| private Drawable mRecentLandIcon; |
| |
| private DelegateViewHelper mDelegateHelper; |
| private DeadZone mDeadZone; |
| private final NavigationBarTransitions mBarTransitions; |
| |
| // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) |
| final static boolean WORKAROUND_INVALID_LAYOUT = true; |
| final static int MSG_CHECK_INVALID_LAYOUT = 8686; |
| |
| // performs manual animation in sync with layout transitions |
| private final NavTransitionListener mTransitionListener = new NavTransitionListener(); |
| |
| private class NavTransitionListener implements TransitionListener { |
| private boolean mBackTransitioning; |
| private boolean mHomeAppearing; |
| private long mStartDelay; |
| private long mDuration; |
| private TimeInterpolator mInterpolator; |
| |
| @Override |
| public void startTransition(LayoutTransition transition, ViewGroup container, |
| View view, int transitionType) { |
| if (view.getId() == R.id.back) { |
| mBackTransitioning = true; |
| } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { |
| mHomeAppearing = true; |
| mStartDelay = transition.getStartDelay(transitionType); |
| mDuration = transition.getDuration(transitionType); |
| mInterpolator = transition.getInterpolator(transitionType); |
| } |
| } |
| |
| @Override |
| public void endTransition(LayoutTransition transition, ViewGroup container, |
| View view, int transitionType) { |
| if (view.getId() == R.id.back) { |
| mBackTransitioning = false; |
| } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { |
| mHomeAppearing = false; |
| } |
| } |
| |
| public void onBackAltCleared() { |
| // When dismissing ime during unlock, force the back button to run the same appearance |
| // animation as home (if we catch this condition early enough). |
| if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE |
| && mHomeAppearing && getHomeButton().getAlpha() == 0) { |
| getBackButton().setAlpha(0); |
| ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); |
| a.setStartDelay(mStartDelay); |
| a.setDuration(mDuration); |
| a.setInterpolator(mInterpolator); |
| a.start(); |
| } |
| } |
| } |
| |
| // simplified click handler to be used when device is in accessibility mode |
| private final OnClickListener mAccessibilityClickListener = new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (v.getId() == R.id.search_light) { |
| KeyguardTouchDelegate.getInstance(getContext()).showAssistant(); |
| } |
| } |
| }; |
| |
| private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) |
| .showInputMethodPicker(); |
| } |
| }; |
| |
| private class H extends Handler { |
| public void handleMessage(Message m) { |
| switch (m.what) { |
| case MSG_CHECK_INVALID_LAYOUT: |
| final String how = "" + m.obj; |
| final int w = getWidth(); |
| final int h = getHeight(); |
| final int vw = mCurrentView.getWidth(); |
| final int vh = mCurrentView.getHeight(); |
| |
| if (h != vh || w != vw) { |
| Log.w(TAG, String.format( |
| "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", |
| how, w, h, vw, vh)); |
| if (WORKAROUND_INVALID_LAYOUT) { |
| requestLayout(); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| public NavigationBarView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| mDisplay = ((WindowManager)context.getSystemService( |
| Context.WINDOW_SERVICE)).getDefaultDisplay(); |
| |
| final Resources res = getContext().getResources(); |
| mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); |
| mVertical = false; |
| mShowMenu = false; |
| mDelegateHelper = new DelegateViewHelper(this); |
| |
| getIcons(res); |
| |
| mBarTransitions = new NavigationBarTransitions(this); |
| } |
| |
| public BarTransitions getBarTransitions() { |
| return mBarTransitions; |
| } |
| |
| public void setDelegateView(View view) { |
| mDelegateHelper.setDelegateView(view); |
| } |
| |
| public void setBar(BaseStatusBar phoneStatusBar) { |
| mDelegateHelper.setBar(phoneStatusBar); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { |
| mDeadZone.poke(event); |
| } |
| if (mDelegateHelper != null) { |
| boolean ret = mDelegateHelper.onInterceptTouchEvent(event); |
| if (ret) return true; |
| } |
| return super.onTouchEvent(event); |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent event) { |
| return mDelegateHelper.onInterceptTouchEvent(event); |
| } |
| |
| private H mHandler = new H(); |
| |
| public View getCurrentView() { |
| return mCurrentView; |
| } |
| |
| public View getRecentsButton() { |
| return mCurrentView.findViewById(R.id.recent_apps); |
| } |
| |
| public View getMenuButton() { |
| return mCurrentView.findViewById(R.id.menu); |
| } |
| |
| public View getBackButton() { |
| return mCurrentView.findViewById(R.id.back); |
| } |
| |
| public View getHomeButton() { |
| return mCurrentView.findViewById(R.id.home); |
| } |
| |
| public View getImeSwitchButton() { |
| return mCurrentView.findViewById(R.id.ime_switcher); |
| } |
| |
| // for when home is disabled, but search isn't |
| public View getSearchLight() { |
| return mCurrentView.findViewById(R.id.search_light); |
| } |
| |
| private void getIcons(Resources res) { |
| mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); |
| mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); |
| mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); |
| mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); |
| mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); |
| mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); |
| } |
| |
| @Override |
| public void setLayoutDirection(int layoutDirection) { |
| getIcons(getContext().getResources()); |
| |
| super.setLayoutDirection(layoutDirection); |
| } |
| |
| public void notifyScreenOn(boolean screenOn) { |
| mScreenOn = screenOn; |
| setDisabledFlags(mDisabledFlags, true); |
| } |
| |
| public void setNavigationIconHints(int hints) { |
| setNavigationIconHints(hints, false); |
| } |
| |
| public void setNavigationIconHints(int hints, boolean force) { |
| if (!force && hints == mNavigationIconHints) return; |
| final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; |
| if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { |
| mTransitionListener.onBackAltCleared(); |
| } |
| if (DEBUG) { |
| android.widget.Toast.makeText(getContext(), |
| "Navigation icon hints = " + hints, |
| 500).show(); |
| } |
| |
| mNavigationIconHints = hints; |
| |
| ((ImageView)getBackButton()).setImageDrawable(backAlt |
| ? (mVertical ? mBackAltLandIcon : mBackAltIcon) |
| : (mVertical ? mBackLandIcon : mBackIcon)); |
| |
| ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); |
| |
| final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); |
| getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); |
| // Update menu button in case the IME state has changed. |
| setMenuVisibility(mShowMenu, true); |
| |
| |
| setDisabledFlags(mDisabledFlags, true); |
| } |
| |
| public void setDisabledFlags(int disabledFlags) { |
| setDisabledFlags(disabledFlags, false); |
| } |
| |
| public void setDisabledFlags(int disabledFlags, boolean force) { |
| if (!force && mDisabledFlags == disabledFlags) return; |
| |
| mDisabledFlags = disabledFlags; |
| |
| final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); |
| final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); |
| final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) |
| && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); |
| final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0); |
| |
| if (SLIPPERY_WHEN_DISABLED) { |
| setSlippery(disableHome && disableRecent && disableBack && disableSearch); |
| } |
| |
| ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); |
| if (navButtons != null) { |
| LayoutTransition lt = navButtons.getLayoutTransition(); |
| if (lt != null) { |
| if (!lt.getTransitionListeners().contains(mTransitionListener)) { |
| lt.addTransitionListener(mTransitionListener); |
| } |
| if (!mScreenOn && mCurrentView != null) { |
| lt.disableTransitionType( |
| LayoutTransition.CHANGE_APPEARING | |
| LayoutTransition.CHANGE_DISAPPEARING | |
| LayoutTransition.APPEARING | |
| LayoutTransition.DISAPPEARING); |
| } |
| } |
| } |
| |
| getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); |
| getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); |
| getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); |
| |
| final boolean showSearch = disableHome && !disableSearch; |
| setVisibleOrGone(getSearchLight(), showSearch); |
| |
| mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); |
| } |
| |
| private void setVisibleOrGone(View view, boolean visible) { |
| if (view != null) { |
| view.setVisibility(visible ? VISIBLE : GONE); |
| } |
| } |
| |
| public void setSlippery(boolean newSlippery) { |
| WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); |
| if (lp != null) { |
| boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; |
| if (!oldSlippery && newSlippery) { |
| lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; |
| } else if (oldSlippery && !newSlippery) { |
| lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; |
| } else { |
| return; |
| } |
| WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); |
| wm.updateViewLayout(this, lp); |
| } |
| } |
| |
| public void setMenuVisibility(final boolean show) { |
| setMenuVisibility(show, false); |
| } |
| |
| public void setMenuVisibility(final boolean show, final boolean force) { |
| if (!force && mShowMenu == show) return; |
| |
| mShowMenu = show; |
| |
| // Only show Menu if IME switcher not shown. |
| final boolean shouldShow = mShowMenu && |
| ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0); |
| getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); |
| } |
| |
| @Override |
| public void onFinishInflate() { |
| mRotatedViews[Surface.ROTATION_0] = |
| mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); |
| |
| mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); |
| |
| mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT |
| ? findViewById(R.id.rot90) |
| : findViewById(R.id.rot270); |
| |
| mCurrentView = mRotatedViews[Surface.ROTATION_0]; |
| |
| getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); |
| |
| watchForAccessibilityChanges(); |
| } |
| |
| private void watchForAccessibilityChanges() { |
| final AccessibilityManager am = |
| (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); |
| |
| // Set the initial state |
| enableAccessibility(am.isTouchExplorationEnabled()); |
| |
| // Watch for changes |
| am.addTouchExplorationStateChangeListener(new TouchExplorationStateChangeListener() { |
| @Override |
| public void onTouchExplorationStateChanged(boolean enabled) { |
| enableAccessibility(enabled); |
| } |
| }); |
| } |
| |
| private void enableAccessibility(boolean touchEnabled) { |
| Log.v(TAG, "touchEnabled:" + touchEnabled); |
| |
| // Add a touch handler or accessibility click listener for camera and search buttons |
| // for all view orientations. |
| final OnClickListener onClickListener = touchEnabled ? mAccessibilityClickListener : null; |
| for (int i = 0; i < mRotatedViews.length; i++) { |
| final View searchLight = mRotatedViews[i].findViewById(R.id.search_light); |
| if (searchLight != null) { |
| searchLight.setOnClickListener(onClickListener); |
| } |
| } |
| } |
| |
| public boolean isVertical() { |
| return mVertical; |
| } |
| |
| public void reorient() { |
| final int rot = mDisplay.getRotation(); |
| for (int i=0; i<4; i++) { |
| mRotatedViews[i].setVisibility(View.GONE); |
| } |
| mCurrentView = mRotatedViews[rot]; |
| mCurrentView.setVisibility(View.VISIBLE); |
| |
| getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); |
| |
| mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); |
| |
| // force the low profile & disabled states into compliance |
| mBarTransitions.init(mVertical); |
| setDisabledFlags(mDisabledFlags, true /* force */); |
| setMenuVisibility(mShowMenu, true /* force */); |
| |
| if (DEBUG) { |
| Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); |
| } |
| |
| // swap to x coordinate if orientation is not in vertical |
| if (mDelegateHelper != null) { |
| mDelegateHelper.setSwapXY(!mVertical); |
| } |
| |
| setNavigationIconHints(mNavigationIconHints, true); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton()); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| if (DEBUG) Log.d(TAG, String.format( |
| "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); |
| |
| final boolean newVertical = w > 0 && h > w; |
| if (newVertical != mVertical) { |
| mVertical = newVertical; |
| //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); |
| reorient(); |
| } |
| |
| postCheckForInvalidLayout("sizeChanged"); |
| super.onSizeChanged(w, h, oldw, oldh); |
| } |
| |
| /* |
| @Override |
| protected void onLayout (boolean changed, int left, int top, int right, int bottom) { |
| if (DEBUG) Log.d(TAG, String.format( |
| "onLayout: %s (%d,%d,%d,%d)", |
| changed?"changed":"notchanged", left, top, right, bottom)); |
| super.onLayout(changed, left, top, right, bottom); |
| } |
| |
| // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else |
| // fails, any touch on the display will fix the layout. |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString()); |
| if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| postCheckForInvalidLayout("touch"); |
| } |
| return super.onInterceptTouchEvent(ev); |
| } |
| */ |
| |
| |
| private String getResourceName(int resId) { |
| if (resId != 0) { |
| final android.content.res.Resources res = getContext().getResources(); |
| try { |
| return res.getResourceName(resId); |
| } catch (android.content.res.Resources.NotFoundException ex) { |
| return "(unknown)"; |
| } |
| } else { |
| return "(null)"; |
| } |
| } |
| |
| private void postCheckForInvalidLayout(final String how) { |
| mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); |
| } |
| |
| private static String visibilityToString(int vis) { |
| switch (vis) { |
| case View.INVISIBLE: |
| return "INVISIBLE"; |
| case View.GONE: |
| return "GONE"; |
| default: |
| return "VISIBLE"; |
| } |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("NavigationBarView {"); |
| final Rect r = new Rect(); |
| final Point size = new Point(); |
| mDisplay.getRealSize(size); |
| |
| pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) |
| + " " + visibilityToString(getVisibility()))); |
| |
| getWindowVisibleDisplayFrame(r); |
| final boolean offscreen = r.right > size.x || r.bottom > size.y; |
| pw.println(" window: " |
| + r.toShortString() |
| + " " + visibilityToString(getWindowVisibility()) |
| + (offscreen ? " OFFSCREEN!" : "")); |
| |
| pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", |
| getResourceName(mCurrentView.getId()), |
| mCurrentView.getWidth(), mCurrentView.getHeight(), |
| visibilityToString(mCurrentView.getVisibility()))); |
| |
| pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s", |
| mDisabledFlags, |
| mVertical ? "true" : "false", |
| mShowMenu ? "true" : "false")); |
| |
| dumpButton(pw, "back", getBackButton()); |
| dumpButton(pw, "home", getHomeButton()); |
| dumpButton(pw, "rcnt", getRecentsButton()); |
| dumpButton(pw, "menu", getMenuButton()); |
| dumpButton(pw, "srch", getSearchLight()); |
| |
| pw.println(" }"); |
| } |
| |
| private static void dumpButton(PrintWriter pw, String caption, View button) { |
| pw.print(" " + caption + ": "); |
| if (button == null) { |
| pw.print("null"); |
| } else { |
| pw.print(PhoneStatusBar.viewInfo(button) |
| + " " + visibilityToString(button.getVisibility()) |
| + " alpha=" + button.getAlpha() |
| ); |
| if (button instanceof KeyButtonView) { |
| pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); |
| pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); |
| } |
| } |
| pw.println(); |
| } |
| |
| } |