blob: 839016d7a1de8671e3bcfcb074eebaaa93b16935 [file] [log] [blame]
/*
* 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.ActivityManagerNative;
import android.app.StatusBarManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.os.RemoteException;
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.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;
// used to disable the camera icon in navbar when disabled by DPM
private boolean mCameraDisabledByDpm;
// 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.camera_button) {
KeyguardTouchDelegate.getInstance(getContext()).launchCamera();
} else if (v.getId() == R.id.search_light) {
KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
}
}
};
private final OnTouchListener mCameraTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View cameraButtonView, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// disable search gesture while interacting with camera
mDelegateHelper.setDisabled(true);
mBarTransitions.setContentVisible(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDelegateHelper.setDisabled(false);
mBarTransitions.setContentVisible(true);
break;
}
return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event);
}
};
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 = mContext.getResources();
mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
mVertical = false;
mShowMenu = false;
mDelegateHelper = new DelegateViewHelper(this);
getIcons(res);
mBarTransitions = new NavigationBarTransitions(this);
mCameraDisabledByDpm = isCameraDisabledByDpm();
watchForDevicePolicyChanges();
}
private void watchForDevicePolicyChanges() {
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiver(new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
post(new Runnable() {
@Override
public void run() {
mCameraDisabledByDpm = isCameraDisabledByDpm();
}
});
}
}, filter);
}
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);
}
// for when home is disabled, but search isn't
public View getSearchLight() {
return mCurrentView.findViewById(R.id.search_light);
}
// shown when keyguard is visible and camera is available
public View getCameraButton() {
return mCurrentView.findViewById(R.id.camera_button);
}
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(mContext.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(mContext,
"Navigation icon hints = " + hints,
500).show();
}
mNavigationIconHints = hints;
((ImageView)getBackButton()).setImageDrawable(backAlt
? (mVertical ? mBackAltLandIcon : mBackAltIcon)
: (mVertical ? mBackLandIcon : mBackIcon));
((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon);
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;
final boolean showCamera = showSearch && !mCameraDisabledByDpm;
setVisibleOrGone(getSearchLight(), showSearch);
setVisibleOrGone(getCameraButton(), showCamera);
mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/);
}
private void setVisibleOrGone(View view, boolean visible) {
if (view != null) {
view.setVisibility(visible ? VISIBLE : GONE);
}
}
private boolean isCameraDisabledByDpm() {
final DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
if (dpm != null) {
try {
final int userId = ActivityManagerNative.getDefault().getCurrentUser().id;
final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId);
final boolean disabledBecauseKeyguardSecure =
(disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0
&& KeyguardTouchDelegate.getInstance(getContext()).isSecure();
return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure;
} catch (RemoteException e) {
Log.e(TAG, "Can't get userId", e);
}
}
return false;
}
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;
getMenuButton().setVisibility(mShowMenu ? 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];
watchForAccessibilityChanges();
}
private void watchForAccessibilityChanges() {
final AccessibilityManager am =
(AccessibilityManager) mContext.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;
final OnTouchListener onTouchListener = touchEnabled ? null : mCameraTouchListener;
boolean hasCamera = false;
for (int i = 0; i < mRotatedViews.length; i++) {
final View cameraButton = mRotatedViews[i].findViewById(R.id.camera_button);
final View searchLight = mRotatedViews[i].findViewById(R.id.search_light);
if (cameraButton != null) {
hasCamera = true;
cameraButton.setOnTouchListener(onTouchListener);
cameraButton.setOnClickListener(onClickListener);
}
if (searchLight != null) {
searchLight.setOnClickListener(onClickListener);
}
}
if (hasCamera) {
// Warm up KeyguardTouchDelegate so it's ready by the time the camera button is touched.
// This will connect to KeyguardService so that touch events are processed.
KeyguardTouchDelegate.getInstance(mContext);
}
}
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);
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());
}
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 = mContext.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());
dumpButton(pw, "cmra", getCameraButton());
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();
}
}