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.LayoutTransition; 20 import android.animation.LayoutTransition.TransitionListener; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.animation.ValueAnimator; 24 import android.app.StatusBarManager; 25 import android.content.Context; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.graphics.Point; 29 import android.graphics.Rect; 30 import android.graphics.drawable.Drawable; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.view.Display; 36 import android.view.MotionEvent; 37 import android.view.Surface; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.view.WindowManager; 41 import android.view.inputmethod.InputMethodManager; 42 import android.widget.ImageView; 43 import android.widget.LinearLayout; 44 import com.android.systemui.R; 45 import com.android.systemui.statusbar.BaseStatusBar; 46 import com.android.systemui.statusbar.DelegateViewHelper; 47 import com.android.systemui.statusbar.policy.DeadZone; 48 import com.android.systemui.statusbar.policy.KeyButtonView; 49 50 import java.io.FileDescriptor; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 54 public class NavigationBarView extends LinearLayout { 55 final static boolean DEBUG = false; 56 final static String TAG = "PhoneStatusBar/NavigationBarView"; 57 58 // slippery nav bar when everything is disabled, e.g. during setup 59 final static boolean SLIPPERY_WHEN_DISABLED = true; 60 61 final Display mDisplay; 62 View mCurrentView = null; 63 View[] mRotatedViews = new View[4]; 64 65 int mBarSize; 66 boolean mVertical; 67 boolean mScreenOn; 68 69 boolean mShowMenu; 70 int mDisabledFlags = 0; 71 int mNavigationIconHints = 0; 72 73 private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; 74 private Drawable mRecentIcon; 75 private Drawable mRecentLandIcon; 76 77 private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper; 78 private DelegateViewHelper mDelegateHelper; 79 private DeadZone mDeadZone; 80 private final NavigationBarTransitions mBarTransitions; 81 82 // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) 83 final static boolean WORKAROUND_INVALID_LAYOUT = true; 84 final static int MSG_CHECK_INVALID_LAYOUT = 8686; 85 86 // performs manual animation in sync with layout transitions 87 private final NavTransitionListener mTransitionListener = new NavTransitionListener(); 88 89 private OnVerticalChangedListener mOnVerticalChangedListener; 90 private boolean mIsLayoutRtl; 91 private boolean mDelegateIntercepted; 92 93 private class NavTransitionListener implements TransitionListener { 94 private boolean mBackTransitioning; 95 private boolean mHomeAppearing; 96 private long mStartDelay; 97 private long mDuration; 98 private TimeInterpolator mInterpolator; 99 100 @Override 101 public void startTransition(LayoutTransition transition, ViewGroup container, 102 View view, int transitionType) { 103 if (view.getId() == R.id.back) { 104 mBackTransitioning = true; 105 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 106 mHomeAppearing = true; 107 mStartDelay = transition.getStartDelay(transitionType); 108 mDuration = transition.getDuration(transitionType); 109 mInterpolator = transition.getInterpolator(transitionType); 110 } 111 } 112 113 @Override 114 public void endTransition(LayoutTransition transition, ViewGroup container, 115 View view, int transitionType) { 116 if (view.getId() == R.id.back) { 117 mBackTransitioning = false; 118 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 119 mHomeAppearing = false; 120 } 121 } 122 123 public void onBackAltCleared() { 124 // When dismissing ime during unlock, force the back button to run the same appearance 125 // animation as home (if we catch this condition early enough). 126 if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE 127 && mHomeAppearing && getHomeButton().getAlpha() == 0) { 128 getBackButton().setAlpha(0); 129 ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); 130 a.setStartDelay(mStartDelay); 131 a.setDuration(mDuration); 132 a.setInterpolator(mInterpolator); 133 a.start(); 134 } 135 } 136 } 137 138 private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { 139 @Override 140 public void onClick(View view) { 141 ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) 142 .showInputMethodPicker(); 143 } 144 }; 145 146 private class H extends Handler { 147 public void handleMessage(Message m) { 148 switch (m.what) { 149 case MSG_CHECK_INVALID_LAYOUT: 150 final String how = "" + m.obj; 151 final int w = getWidth(); 152 final int h = getHeight(); 153 final int vw = mCurrentView.getWidth(); 154 final int vh = mCurrentView.getHeight(); 155 156 if (h != vh || w != vw) { 157 Log.w(TAG, String.format( 158 "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", 159 how, w, h, vw, vh)); 160 if (WORKAROUND_INVALID_LAYOUT) { 161 requestLayout(); 162 } 163 } 164 break; 165 } 166 } 167 } 168 169 public NavigationBarView(Context context, AttributeSet attrs) { 170 super(context, attrs); 171 172 mDisplay = ((WindowManager)context.getSystemService( 173 Context.WINDOW_SERVICE)).getDefaultDisplay(); 174 175 final Resources res = getContext().getResources(); 176 mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); 177 mVertical = false; 178 mShowMenu = false; 179 mDelegateHelper = new DelegateViewHelper(this); 180 mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context); 181 182 getIcons(res); 183 184 mBarTransitions = new NavigationBarTransitions(this); 185 } 186 187 public BarTransitions getBarTransitions() { 188 return mBarTransitions; 189 } 190 191 public void setDelegateView(View view) { 192 mDelegateHelper.setDelegateView(view); 193 } 194 195 public void setBar(BaseStatusBar phoneStatusBar) { 196 mTaskSwitchHelper.setBar(phoneStatusBar); 197 mDelegateHelper.setBar(phoneStatusBar); 198 } 199 200 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) { 201 mOnVerticalChangedListener = onVerticalChangedListener; 202 notifyVerticalChangedListener(mVertical); 203 } 204 205 @Override 206 public boolean onTouchEvent(MotionEvent event) { 207 initDownStates(event); 208 if (!mDelegateIntercepted && mTaskSwitchHelper.onTouchEvent(event)) { 209 return true; 210 } 211 if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { 212 mDeadZone.poke(event); 213 } 214 if (mDelegateHelper != null && mDelegateIntercepted) { 215 boolean ret = mDelegateHelper.onInterceptTouchEvent(event); 216 if (ret) return true; 217 } 218 return super.onTouchEvent(event); 219 } 220 221 private void initDownStates(MotionEvent ev) { 222 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 223 mDelegateIntercepted = false; 224 } 225 } 226 227 @Override 228 public boolean onInterceptTouchEvent(MotionEvent event) { 229 initDownStates(event); 230 boolean intercept = mTaskSwitchHelper.onInterceptTouchEvent(event); 231 if (!intercept) { 232 mDelegateIntercepted = mDelegateHelper.onInterceptTouchEvent(event); 233 intercept = mDelegateIntercepted; 234 } else { 235 MotionEvent cancelEvent = MotionEvent.obtain(event); 236 cancelEvent.setAction(MotionEvent.ACTION_CANCEL); 237 mDelegateHelper.onInterceptTouchEvent(cancelEvent); 238 cancelEvent.recycle(); 239 } 240 return intercept; 241 } 242 243 private H mHandler = new H(); 244 245 public View getCurrentView() { 246 return mCurrentView; 247 } 248 249 public View getRecentsButton() { 250 return mCurrentView.findViewById(R.id.recent_apps); 251 } 252 253 public View getMenuButton() { 254 return mCurrentView.findViewById(R.id.menu); 255 } 256 257 public View getBackButton() { 258 return mCurrentView.findViewById(R.id.back); 259 } 260 261 public View getHomeButton() { 262 return mCurrentView.findViewById(R.id.home); 263 } 264 265 public View getImeSwitchButton() { 266 return mCurrentView.findViewById(R.id.ime_switcher); 267 } 268 269 private void getIcons(Resources res) { 270 mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); 271 mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); 272 mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 273 mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 274 mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); 275 mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); 276 } 277 278 @Override 279 public void setLayoutDirection(int layoutDirection) { 280 getIcons(getContext().getResources()); 281 282 super.setLayoutDirection(layoutDirection); 283 } 284 285 public void notifyScreenOn(boolean screenOn) { 286 mScreenOn = screenOn; 287 setDisabledFlags(mDisabledFlags, true); 288 } 289 290 public void setNavigationIconHints(int hints) { 291 setNavigationIconHints(hints, false); 292 } 293 294 public void setNavigationIconHints(int hints, boolean force) { 295 if (!force && hints == mNavigationIconHints) return; 296 final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 297 if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { 298 mTransitionListener.onBackAltCleared(); 299 } 300 if (DEBUG) { 301 android.widget.Toast.makeText(getContext(), 302 "Navigation icon hints = " + hints, 303 500).show(); 304 } 305 306 mNavigationIconHints = hints; 307 308 ((ImageView)getBackButton()).setImageDrawable(backAlt 309 ? (mVertical ? mBackAltLandIcon : mBackAltIcon) 310 : (mVertical ? mBackLandIcon : mBackIcon)); 311 312 ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); 313 314 final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); 315 getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); 316 // Update menu button in case the IME state has changed. 317 setMenuVisibility(mShowMenu, true); 318 319 320 setDisabledFlags(mDisabledFlags, true); 321 } 322 323 public void setDisabledFlags(int disabledFlags) { 324 setDisabledFlags(disabledFlags, false); 325 } 326 327 public void setDisabledFlags(int disabledFlags, boolean force) { 328 if (!force && mDisabledFlags == disabledFlags) return; 329 330 mDisabledFlags = disabledFlags; 331 332 final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 333 final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); 334 final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 335 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); 336 final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0); 337 338 if (SLIPPERY_WHEN_DISABLED) { 339 setSlippery(disableHome && disableRecent && disableBack && disableSearch); 340 } 341 342 ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); 343 if (navButtons != null) { 344 LayoutTransition lt = navButtons.getLayoutTransition(); 345 if (lt != null) { 346 if (!lt.getTransitionListeners().contains(mTransitionListener)) { 347 lt.addTransitionListener(mTransitionListener); 348 } 349 if (!mScreenOn && mCurrentView != null) { 350 lt.disableTransitionType( 351 LayoutTransition.CHANGE_APPEARING | 352 LayoutTransition.CHANGE_DISAPPEARING | 353 LayoutTransition.APPEARING | 354 LayoutTransition.DISAPPEARING); 355 } 356 } 357 } 358 359 getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 360 getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 361 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 362 363 mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); 364 } 365 366 private void setVisibleOrGone(View view, boolean visible) { 367 if (view != null) { 368 view.setVisibility(visible ? VISIBLE : GONE); 369 } 370 } 371 372 public void setSlippery(boolean newSlippery) { 373 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 374 if (lp != null) { 375 boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; 376 if (!oldSlippery && newSlippery) { 377 lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; 378 } else if (oldSlippery && !newSlippery) { 379 lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; 380 } else { 381 return; 382 } 383 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); 384 wm.updateViewLayout(this, lp); 385 } 386 } 387 388 public void setMenuVisibility(final boolean show) { 389 setMenuVisibility(show, false); 390 } 391 392 public void setMenuVisibility(final boolean show, final boolean force) { 393 if (!force && mShowMenu == show) return; 394 395 mShowMenu = show; 396 397 // Only show Menu if IME switcher not shown. 398 final boolean shouldShow = mShowMenu && 399 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0); 400 getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); 401 } 402 403 @Override 404 public void onFinishInflate() { 405 mRotatedViews[Surface.ROTATION_0] = 406 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); 407 408 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); 409 410 mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90]; 411 412 mCurrentView = mRotatedViews[Surface.ROTATION_0]; 413 414 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); 415 416 updateRTLOrder(); 417 } 418 419 public boolean isVertical() { 420 return mVertical; 421 } 422 423 public void reorient() { 424 final int rot = mDisplay.getRotation(); 425 for (int i=0; i<4; i++) { 426 mRotatedViews[i].setVisibility(View.GONE); 427 } 428 mCurrentView = mRotatedViews[rot]; 429 mCurrentView.setVisibility(View.VISIBLE); 430 431 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); 432 433 mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); 434 435 // force the low profile & disabled states into compliance 436 mBarTransitions.init(mVertical); 437 setDisabledFlags(mDisabledFlags, true /* force */); 438 setMenuVisibility(mShowMenu, true /* force */); 439 440 if (DEBUG) { 441 Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); 442 } 443 444 // swap to x coordinate if orientation is not in vertical 445 if (mDelegateHelper != null) { 446 mDelegateHelper.setSwapXY(mVertical); 447 } 448 updateTaskSwitchHelper(); 449 450 setNavigationIconHints(mNavigationIconHints, true); 451 } 452 453 private void updateTaskSwitchHelper() { 454 boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); 455 mTaskSwitchHelper.setBarState(mVertical, isRtl); 456 } 457 458 @Override 459 protected void onLayout(boolean changed, int l, int t, int r, int b) { 460 super.onLayout(changed, l, t, r, b); 461 mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton()); 462 } 463 464 @Override 465 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 466 if (DEBUG) Log.d(TAG, String.format( 467 "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); 468 469 final boolean newVertical = w > 0 && h > w; 470 if (newVertical != mVertical) { 471 mVertical = newVertical; 472 //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); 473 reorient(); 474 notifyVerticalChangedListener(newVertical); 475 } 476 477 postCheckForInvalidLayout("sizeChanged"); 478 super.onSizeChanged(w, h, oldw, oldh); 479 } 480 481 private void notifyVerticalChangedListener(boolean newVertical) { 482 if (mOnVerticalChangedListener != null) { 483 mOnVerticalChangedListener.onVerticalChanged(newVertical); 484 } 485 } 486 487 @Override 488 protected void onConfigurationChanged(Configuration newConfig) { 489 super.onConfigurationChanged(newConfig); 490 updateRTLOrder(); 491 updateTaskSwitchHelper(); 492 } 493 494 /** 495 * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we 496 * have to do it manually 497 */ 498 private void updateRTLOrder() { 499 boolean isLayoutRtl = getResources().getConfiguration() 500 .getLayoutDirection() == LAYOUT_DIRECTION_RTL; 501 if (mIsLayoutRtl != isLayoutRtl) { 502 503 // We swap all children of the 90 and 270 degree layouts, since they are vertical 504 View rotation90 = mRotatedViews[Surface.ROTATION_90]; 505 swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons)); 506 507 View rotation270 = mRotatedViews[Surface.ROTATION_270]; 508 if (rotation90 != rotation270) { 509 swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons)); 510 } 511 mIsLayoutRtl = isLayoutRtl; 512 } 513 } 514 515 516 /** 517 * Swaps the children order of a LinearLayout if it's orientation is Vertical 518 * 519 * @param group The LinearLayout to swap the children from. 520 */ 521 private void swapChildrenOrderIfVertical(View group) { 522 if (group instanceof LinearLayout) { 523 LinearLayout linearLayout = (LinearLayout) group; 524 if (linearLayout.getOrientation() == VERTICAL) { 525 int childCount = linearLayout.getChildCount(); 526 ArrayList<View> childList = new ArrayList<>(childCount); 527 for (int i = 0; i < childCount; i++) { 528 childList.add(linearLayout.getChildAt(i)); 529 } 530 linearLayout.removeAllViews(); 531 for (int i = childCount - 1; i >= 0; i--) { 532 linearLayout.addView(childList.get(i)); 533 } 534 } 535 } 536 } 537 538 /* 539 @Override 540 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 541 if (DEBUG) Log.d(TAG, String.format( 542 "onLayout: %s (%d,%d,%d,%d)", 543 changed?"changed":"notchanged", left, top, right, bottom)); 544 super.onLayout(changed, left, top, right, bottom); 545 } 546 547 // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else 548 // fails, any touch on the display will fix the layout. 549 @Override 550 public boolean onInterceptTouchEvent(MotionEvent ev) { 551 if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString()); 552 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 553 postCheckForInvalidLayout("touch"); 554 } 555 return super.onInterceptTouchEvent(ev); 556 } 557 */ 558 559 560 private String getResourceName(int resId) { 561 if (resId != 0) { 562 final android.content.res.Resources res = getContext().getResources(); 563 try { 564 return res.getResourceName(resId); 565 } catch (android.content.res.Resources.NotFoundException ex) { 566 return "(unknown)"; 567 } 568 } else { 569 return "(null)"; 570 } 571 } 572 573 private void postCheckForInvalidLayout(final String how) { 574 mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); 575 } 576 577 private static String visibilityToString(int vis) { 578 switch (vis) { 579 case View.INVISIBLE: 580 return "INVISIBLE"; 581 case View.GONE: 582 return "GONE"; 583 default: 584 return "VISIBLE"; 585 } 586 } 587 588 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 589 pw.println("NavigationBarView {"); 590 final Rect r = new Rect(); 591 final Point size = new Point(); 592 mDisplay.getRealSize(size); 593 594 pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) 595 + " " + visibilityToString(getVisibility()))); 596 597 getWindowVisibleDisplayFrame(r); 598 final boolean offscreen = r.right > size.x || r.bottom > size.y; 599 pw.println(" window: " 600 + r.toShortString() 601 + " " + visibilityToString(getWindowVisibility()) 602 + (offscreen ? " OFFSCREEN!" : "")); 603 604 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", 605 getResourceName(mCurrentView.getId()), 606 mCurrentView.getWidth(), mCurrentView.getHeight(), 607 visibilityToString(mCurrentView.getVisibility()))); 608 609 pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s", 610 mDisabledFlags, 611 mVertical ? "true" : "false", 612 mShowMenu ? "true" : "false")); 613 614 dumpButton(pw, "back", getBackButton()); 615 dumpButton(pw, "home", getHomeButton()); 616 dumpButton(pw, "rcnt", getRecentsButton()); 617 dumpButton(pw, "menu", getMenuButton()); 618 619 pw.println(" }"); 620 } 621 622 private static void dumpButton(PrintWriter pw, String caption, View button) { 623 pw.print(" " + caption + ": "); 624 if (button == null) { 625 pw.print("null"); 626 } else { 627 pw.print(PhoneStatusBar.viewInfo(button) 628 + " " + visibilityToString(button.getVisibility()) 629 + " alpha=" + button.getAlpha() 630 ); 631 if (button instanceof KeyButtonView) { 632 pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); 633 pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); 634 } 635 } 636 pw.println(); 637 } 638 639 public interface OnVerticalChangedListener { 640 void onVerticalChanged(boolean isVertical); 641 } 642 } 643