1/* 2 * Copyright (C) 2006 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 17package android.view; 18 19import com.android.internal.view.IInputMethodCallback; 20import com.android.internal.view.IInputMethodSession; 21 22import android.graphics.Canvas; 23import android.graphics.PixelFormat; 24import android.graphics.PorterDuff; 25import android.graphics.Rect; 26import android.graphics.Region; 27import android.os.*; 28import android.os.Process; 29import android.os.SystemProperties; 30import android.util.AndroidRuntimeException; 31import android.util.Config; 32import android.util.DisplayMetrics; 33import android.util.Log; 34import android.util.EventLog; 35import android.util.SparseArray; 36import android.view.View.MeasureSpec; 37import android.view.accessibility.AccessibilityEvent; 38import android.view.accessibility.AccessibilityManager; 39import android.view.inputmethod.InputConnection; 40import android.view.inputmethod.InputMethodManager; 41import android.widget.Scroller; 42import android.content.pm.PackageManager; 43import android.content.res.CompatibilityInfo; 44import android.content.res.Resources; 45import android.content.Context; 46import android.app.ActivityManagerNative; 47import android.Manifest; 48import android.media.AudioManager; 49 50import java.lang.ref.WeakReference; 51import java.io.IOException; 52import java.io.OutputStream; 53import java.util.ArrayList; 54 55import javax.microedition.khronos.egl.*; 56import javax.microedition.khronos.opengles.*; 57import static javax.microedition.khronos.opengles.GL10.*; 58 59/** 60 * The top of a view hierarchy, implementing the needed protocol between View 61 * and the WindowManager. This is for the most part an internal implementation 62 * detail of {@link WindowManagerImpl}. 63 * 64 * {@hide} 65 */ 66@SuppressWarnings({"EmptyCatchBlock"}) 67public final class ViewRoot extends Handler implements ViewParent, 68 View.AttachInfo.Callbacks { 69 private static final String TAG = "ViewRoot"; 70 private static final boolean DBG = false; 71 @SuppressWarnings({"ConstantConditionalExpression"}) 72 private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV; 73 /** @noinspection PointlessBooleanExpression*/ 74 private static final boolean DEBUG_DRAW = false || LOCAL_LOGV; 75 private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV; 76 private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV; 77 private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV; 78 private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV; 79 private static final boolean DEBUG_IMF = false || LOCAL_LOGV; 80 private static final boolean WATCH_POINTER = false; 81 82 private static final boolean MEASURE_LATENCY = false; 83 private static LatencyTimer lt; 84 85 /** 86 * Maximum time we allow the user to roll the trackball enough to generate 87 * a key event, before resetting the counters. 88 */ 89 static final int MAX_TRACKBALL_DELAY = 250; 90 91 static long sInstanceCount = 0; 92 93 static IWindowSession sWindowSession; 94 95 static final Object mStaticInit = new Object(); 96 static boolean mInitialized = false; 97 98 static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); 99 100 private static int sDrawTime; 101 102 long mLastTrackballTime = 0; 103 final TrackballAxis mTrackballAxisX = new TrackballAxis(); 104 final TrackballAxis mTrackballAxisY = new TrackballAxis(); 105 106 final int[] mTmpLocation = new int[2]; 107 108 final InputMethodCallback mInputMethodCallback; 109 final SparseArray<Object> mPendingEvents = new SparseArray<Object>(); 110 int mPendingEventSeq = 0; 111 112 final Thread mThread; 113 114 final WindowLeaked mLocation; 115 116 final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); 117 118 final W mWindow; 119 120 View mView; 121 View mFocusedView; 122 View mRealFocusedView; // this is not set to null in touch mode 123 int mViewVisibility; 124 boolean mAppVisible = true; 125 126 final Region mTransparentRegion; 127 final Region mPreviousTransparentRegion; 128 129 int mWidth; 130 int mHeight; 131 Rect mDirty; // will be a graphics.Region soon 132 boolean mIsAnimating; 133 134 CompatibilityInfo.Translator mTranslator; 135 136 final View.AttachInfo mAttachInfo; 137 138 final Rect mTempRect; // used in the transaction to not thrash the heap. 139 final Rect mVisRect; // used to retrieve visible rect of focused view. 140 141 boolean mTraversalScheduled; 142 boolean mWillDrawSoon; 143 boolean mLayoutRequested; 144 boolean mFirst; 145 boolean mReportNextDraw; 146 boolean mFullRedrawNeeded; 147 boolean mNewSurfaceNeeded; 148 boolean mHasHadWindowFocus; 149 boolean mLastWasImTarget; 150 151 boolean mWindowAttributesChanged = false; 152 153 // These can be accessed by any thread, must be protected with a lock. 154 // Surface can never be reassigned or cleared (use Surface.clear()). 155 private final Surface mSurface = new Surface(); 156 157 boolean mAdded; 158 boolean mAddedTouchMode; 159 160 /*package*/ int mAddNesting; 161 162 // These are accessed by multiple threads. 163 final Rect mWinFrame; // frame given by window manager. 164 165 final Rect mPendingVisibleInsets = new Rect(); 166 final Rect mPendingContentInsets = new Rect(); 167 final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets 168 = new ViewTreeObserver.InternalInsetsInfo(); 169 170 boolean mScrollMayChange; 171 int mSoftInputMode; 172 View mLastScrolledFocus; 173 int mScrollY; 174 int mCurScrollY; 175 Scroller mScroller; 176 177 EGL10 mEgl; 178 EGLDisplay mEglDisplay; 179 EGLContext mEglContext; 180 EGLSurface mEglSurface; 181 GL11 mGL; 182 Canvas mGlCanvas; 183 boolean mUseGL; 184 boolean mGlWanted; 185 186 final ViewConfiguration mViewConfiguration; 187 188 /** 189 * see {@link #playSoundEffect(int)} 190 */ 191 AudioManager mAudioManager; 192 193 private final int mDensity; 194 195 public static IWindowSession getWindowSession(Looper mainLooper) { 196 synchronized (mStaticInit) { 197 if (!mInitialized) { 198 try { 199 InputMethodManager imm = InputMethodManager.getInstance(mainLooper); 200 sWindowSession = IWindowManager.Stub.asInterface( 201 ServiceManager.getService("window")) 202 .openSession(imm.getClient(), imm.getInputContext()); 203 mInitialized = true; 204 } catch (RemoteException e) { 205 } 206 } 207 return sWindowSession; 208 } 209 } 210 211 public ViewRoot(Context context) { 212 super(); 213 214 if (MEASURE_LATENCY && lt == null) { 215 lt = new LatencyTimer(100, 1000); 216 } 217 218 ++sInstanceCount; 219 220 // Initialize the statics when this class is first instantiated. This is 221 // done here instead of in the static block because Zygote does not 222 // allow the spawning of threads. 223 getWindowSession(context.getMainLooper()); 224 225 mThread = Thread.currentThread(); 226 mLocation = new WindowLeaked(null); 227 mLocation.fillInStackTrace(); 228 mWidth = -1; 229 mHeight = -1; 230 mDirty = new Rect(); 231 mTempRect = new Rect(); 232 mVisRect = new Rect(); 233 mWinFrame = new Rect(); 234 mWindow = new W(this, context); 235 mInputMethodCallback = new InputMethodCallback(this); 236 mViewVisibility = View.GONE; 237 mTransparentRegion = new Region(); 238 mPreviousTransparentRegion = new Region(); 239 mFirst = true; // true for the first time the view is added 240 mAdded = false; 241 mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this); 242 mViewConfiguration = ViewConfiguration.get(context); 243 mDensity = context.getResources().getDisplayMetrics().densityDpi; 244 } 245 246 @Override 247 protected void finalize() throws Throwable { 248 super.finalize(); 249 --sInstanceCount; 250 } 251 252 public static long getInstanceCount() { 253 return sInstanceCount; 254 } 255 256 // FIXME for perf testing only 257 private boolean mProfile = false; 258 259 /** 260 * Call this to profile the next traversal call. 261 * FIXME for perf testing only. Remove eventually 262 */ 263 public void profile() { 264 mProfile = true; 265 } 266 267 /** 268 * Indicates whether we are in touch mode. Calling this method triggers an IPC 269 * call and should be avoided whenever possible. 270 * 271 * @return True, if the device is in touch mode, false otherwise. 272 * 273 * @hide 274 */ 275 static boolean isInTouchMode() { 276 if (mInitialized) { 277 try { 278 return sWindowSession.getInTouchMode(); 279 } catch (RemoteException e) { 280 } 281 } 282 return false; 283 } 284 285 private void initializeGL() { 286 initializeGLInner(); 287 int err = mEgl.eglGetError(); 288 if (err != EGL10.EGL_SUCCESS) { 289 // give-up on using GL 290 destroyGL(); 291 mGlWanted = false; 292 } 293 } 294 295 private void initializeGLInner() { 296 final EGL10 egl = (EGL10) EGLContext.getEGL(); 297 mEgl = egl; 298 299 /* 300 * Get to the default display. 301 */ 302 final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 303 mEglDisplay = eglDisplay; 304 305 /* 306 * We can now initialize EGL for that display 307 */ 308 int[] version = new int[2]; 309 egl.eglInitialize(eglDisplay, version); 310 311 /* 312 * Specify a configuration for our opengl session 313 * and grab the first configuration that matches is 314 */ 315 final int[] configSpec = { 316 EGL10.EGL_RED_SIZE, 5, 317 EGL10.EGL_GREEN_SIZE, 6, 318 EGL10.EGL_BLUE_SIZE, 5, 319 EGL10.EGL_DEPTH_SIZE, 0, 320 EGL10.EGL_NONE 321 }; 322 final EGLConfig[] configs = new EGLConfig[1]; 323 final int[] num_config = new int[1]; 324 egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, num_config); 325 final EGLConfig config = configs[0]; 326 327 /* 328 * Create an OpenGL ES context. This must be done only once, an 329 * OpenGL context is a somewhat heavy object. 330 */ 331 final EGLContext context = egl.eglCreateContext(eglDisplay, config, 332 EGL10.EGL_NO_CONTEXT, null); 333 mEglContext = context; 334 335 /* 336 * Create an EGL surface we can render into. 337 */ 338 final EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, mHolder, null); 339 mEglSurface = surface; 340 341 /* 342 * Before we can issue GL commands, we need to make sure 343 * the context is current and bound to a surface. 344 */ 345 egl.eglMakeCurrent(eglDisplay, surface, surface, context); 346 347 /* 348 * Get to the appropriate GL interface. 349 * This is simply done by casting the GL context to either 350 * GL10 or GL11. 351 */ 352 final GL11 gl = (GL11) context.getGL(); 353 mGL = gl; 354 mGlCanvas = new Canvas(gl); 355 mUseGL = true; 356 } 357 358 private void destroyGL() { 359 // inform skia that the context is gone 360 nativeAbandonGlCaches(); 361 362 mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, 363 EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 364 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 365 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 366 mEgl.eglTerminate(mEglDisplay); 367 mEglContext = null; 368 mEglSurface = null; 369 mEglDisplay = null; 370 mEgl = null; 371 mGlCanvas = null; 372 mGL = null; 373 mUseGL = false; 374 } 375 376 private void checkEglErrors() { 377 if (mUseGL) { 378 int err = mEgl.eglGetError(); 379 if (err != EGL10.EGL_SUCCESS) { 380 // something bad has happened revert to 381 // normal rendering. 382 destroyGL(); 383 if (err != EGL11.EGL_CONTEXT_LOST) { 384 // we'll try again if it was context lost 385 mGlWanted = false; 386 } 387 } 388 } 389 } 390 391 /** 392 * We have one child 393 */ 394 public void setView(View view, WindowManager.LayoutParams attrs, 395 View panelParentView) { 396 synchronized (this) { 397 if (mView == null) { 398 mView = view; 399 mWindowAttributes.copyFrom(attrs); 400 attrs = mWindowAttributes; 401 Resources resources = mView.getContext().getResources(); 402 CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo(); 403 mTranslator = compatibilityInfo.getTranslator(); 404 405 if (mTranslator != null || !compatibilityInfo.supportsScreen()) { 406 mSurface.setCompatibleDisplayMetrics(resources.getDisplayMetrics(), 407 mTranslator); 408 } 409 410 boolean restore = false; 411 if (mTranslator != null) { 412 restore = true; 413 attrs.backup(); 414 mTranslator.translateWindowLayout(attrs); 415 } 416 if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); 417 418 if (!compatibilityInfo.supportsScreen()) { 419 attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; 420 } 421 422 mSoftInputMode = attrs.softInputMode; 423 mWindowAttributesChanged = true; 424 mAttachInfo.mRootView = view; 425 mAttachInfo.mScalingRequired = mTranslator != null; 426 mAttachInfo.mApplicationScale = 427 mTranslator == null ? 1.0f : mTranslator.applicationScale; 428 if (panelParentView != null) { 429 mAttachInfo.mPanelParentWindowToken 430 = panelParentView.getApplicationWindowToken(); 431 } 432 mAdded = true; 433 int res; /* = WindowManagerImpl.ADD_OKAY; */ 434 435 // Schedule the first layout -before- adding to the window 436 // manager, to make sure we do the relayout before receiving 437 // any other events from the system. 438 requestLayout(); 439 try { 440 res = sWindowSession.add(mWindow, mWindowAttributes, 441 getHostVisibility(), mAttachInfo.mContentInsets); 442 } catch (RemoteException e) { 443 mAdded = false; 444 mView = null; 445 mAttachInfo.mRootView = null; 446 unscheduleTraversals(); 447 throw new RuntimeException("Adding window failed", e); 448 } finally { 449 if (restore) { 450 attrs.restore(); 451 } 452 } 453 454 if (mTranslator != null) { 455 mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); 456 } 457 mPendingContentInsets.set(mAttachInfo.mContentInsets); 458 mPendingVisibleInsets.set(0, 0, 0, 0); 459 if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow); 460 if (res < WindowManagerImpl.ADD_OKAY) { 461 mView = null; 462 mAttachInfo.mRootView = null; 463 mAdded = false; 464 unscheduleTraversals(); 465 switch (res) { 466 case WindowManagerImpl.ADD_BAD_APP_TOKEN: 467 case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN: 468 throw new WindowManagerImpl.BadTokenException( 469 "Unable to add window -- token " + attrs.token 470 + " is not valid; is your activity running?"); 471 case WindowManagerImpl.ADD_NOT_APP_TOKEN: 472 throw new WindowManagerImpl.BadTokenException( 473 "Unable to add window -- token " + attrs.token 474 + " is not for an application"); 475 case WindowManagerImpl.ADD_APP_EXITING: 476 throw new WindowManagerImpl.BadTokenException( 477 "Unable to add window -- app for token " + attrs.token 478 + " is exiting"); 479 case WindowManagerImpl.ADD_DUPLICATE_ADD: 480 throw new WindowManagerImpl.BadTokenException( 481 "Unable to add window -- window " + mWindow 482 + " has already been added"); 483 case WindowManagerImpl.ADD_STARTING_NOT_NEEDED: 484 // Silently ignore -- we would have just removed it 485 // right away, anyway. 486 return; 487 case WindowManagerImpl.ADD_MULTIPLE_SINGLETON: 488 throw new WindowManagerImpl.BadTokenException( 489 "Unable to add window " + mWindow + 490 " -- another window of this type already exists"); 491 case WindowManagerImpl.ADD_PERMISSION_DENIED: 492 throw new WindowManagerImpl.BadTokenException( 493 "Unable to add window " + mWindow + 494 " -- permission denied for this window type"); 495 } 496 throw new RuntimeException( 497 "Unable to add window -- unknown error code " + res); 498 } 499 view.assignParent(this); 500 mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; 501 mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; 502 } 503 } 504 } 505 506 public View getView() { 507 return mView; 508 } 509 510 final WindowLeaked getLocation() { 511 return mLocation; 512 } 513 514 void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { 515 synchronized (this) { 516 int oldSoftInputMode = mWindowAttributes.softInputMode; 517 // preserve compatible window flag if exists. 518 int compatibleWindowFlag = 519 mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; 520 mWindowAttributes.copyFrom(attrs); 521 mWindowAttributes.flags |= compatibleWindowFlag; 522 523 if (newView) { 524 mSoftInputMode = attrs.softInputMode; 525 requestLayout(); 526 } 527 // Don't lose the mode we last auto-computed. 528 if ((attrs.softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 529 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { 530 mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode 531 & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 532 | (oldSoftInputMode 533 & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST); 534 } 535 mWindowAttributesChanged = true; 536 scheduleTraversals(); 537 } 538 } 539 540 void handleAppVisibility(boolean visible) { 541 if (mAppVisible != visible) { 542 mAppVisible = visible; 543 scheduleTraversals(); 544 } 545 } 546 547 void handleGetNewSurface() { 548 mNewSurfaceNeeded = true; 549 mFullRedrawNeeded = true; 550 scheduleTraversals(); 551 } 552 553 /** 554 * {@inheritDoc} 555 */ 556 public void requestLayout() { 557 checkThread(); 558 mLayoutRequested = true; 559 scheduleTraversals(); 560 } 561 562 /** 563 * {@inheritDoc} 564 */ 565 public boolean isLayoutRequested() { 566 return mLayoutRequested; 567 } 568 569 public void invalidateChild(View child, Rect dirty) { 570 checkThread(); 571 if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); 572 if (mCurScrollY != 0 || mTranslator != null) { 573 mTempRect.set(dirty); 574 dirty = mTempRect; 575 if (mCurScrollY != 0) { 576 dirty.offset(0, -mCurScrollY); 577 } 578 if (mTranslator != null) { 579 mTranslator.translateRectInAppWindowToScreen(dirty); 580 } 581 if (mAttachInfo.mScalingRequired) { 582 dirty.inset(-1, -1); 583 } 584 } 585 mDirty.union(dirty); 586 if (!mWillDrawSoon) { 587 scheduleTraversals(); 588 } 589 } 590 591 public ViewParent getParent() { 592 return null; 593 } 594 595 public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { 596 invalidateChild(null, dirty); 597 return null; 598 } 599 600 public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { 601 if (child != mView) { 602 throw new RuntimeException("child is not mine, honest!"); 603 } 604 // Note: don't apply scroll offset, because we want to know its 605 // visibility in the virtual canvas being given to the view hierarchy. 606 return r.intersect(0, 0, mWidth, mHeight); 607 } 608 609 public void bringChildToFront(View child) { 610 } 611 612 public void scheduleTraversals() { 613 if (!mTraversalScheduled) { 614 mTraversalScheduled = true; 615 sendEmptyMessage(DO_TRAVERSAL); 616 } 617 } 618 619 public void unscheduleTraversals() { 620 if (mTraversalScheduled) { 621 mTraversalScheduled = false; 622 removeMessages(DO_TRAVERSAL); 623 } 624 } 625 626 int getHostVisibility() { 627 return mAppVisible ? mView.getVisibility() : View.GONE; 628 } 629 630 private void performTraversals() { 631 // cache mView since it is used so much below... 632 final View host = mView; 633 634 if (DBG) { 635 System.out.println("======================================"); 636 System.out.println("performTraversals"); 637 host.debug(); 638 } 639 640 if (host == null || !mAdded) 641 return; 642 643 mTraversalScheduled = false; 644 mWillDrawSoon = true; 645 boolean windowResizesToFitContent = false; 646 boolean fullRedrawNeeded = mFullRedrawNeeded; 647 boolean newSurface = false; 648 WindowManager.LayoutParams lp = mWindowAttributes; 649 650 int desiredWindowWidth; 651 int desiredWindowHeight; 652 int childWidthMeasureSpec; 653 int childHeightMeasureSpec; 654 655 final View.AttachInfo attachInfo = mAttachInfo; 656 657 final int viewVisibility = getHostVisibility(); 658 boolean viewVisibilityChanged = mViewVisibility != viewVisibility 659 || mNewSurfaceNeeded; 660 661 float appScale = mAttachInfo.mApplicationScale; 662 663 WindowManager.LayoutParams params = null; 664 if (mWindowAttributesChanged) { 665 mWindowAttributesChanged = false; 666 params = lp; 667 } 668 Rect frame = mWinFrame; 669 if (mFirst) { 670 fullRedrawNeeded = true; 671 mLayoutRequested = true; 672 673 DisplayMetrics packageMetrics = 674 mView.getContext().getResources().getDisplayMetrics(); 675 desiredWindowWidth = packageMetrics.widthPixels; 676 desiredWindowHeight = packageMetrics.heightPixels; 677 678 // For the very first time, tell the view hierarchy that it 679 // is attached to the window. Note that at this point the surface 680 // object is not initialized to its backing store, but soon it 681 // will be (assuming the window is visible). 682 attachInfo.mSurface = mSurface; 683 attachInfo.mTranslucentWindow = lp.format != PixelFormat.OPAQUE; 684 attachInfo.mHasWindowFocus = false; 685 attachInfo.mWindowVisibility = viewVisibility; 686 attachInfo.mRecomputeGlobalAttributes = false; 687 attachInfo.mKeepScreenOn = false; 688 viewVisibilityChanged = false; 689 host.dispatchAttachedToWindow(attachInfo, 0); 690 //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); 691 692 } else { 693 desiredWindowWidth = frame.width(); 694 desiredWindowHeight = frame.height(); 695 if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { 696 if (DEBUG_ORIENTATION) Log.v("ViewRoot", 697 "View " + host + " resized to: " + frame); 698 fullRedrawNeeded = true; 699 mLayoutRequested = true; 700 windowResizesToFitContent = true; 701 } 702 } 703 704 if (viewVisibilityChanged) { 705 attachInfo.mWindowVisibility = viewVisibility; 706 host.dispatchWindowVisibilityChanged(viewVisibility); 707 if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { 708 if (mUseGL) { 709 destroyGL(); 710 } 711 } 712 if (viewVisibility == View.GONE) { 713 // After making a window gone, we will count it as being 714 // shown for the first time the next time it gets focus. 715 mHasHadWindowFocus = false; 716 } 717 } 718 719 boolean insetsChanged = false; 720 721 if (mLayoutRequested) { 722 // Execute enqueued actions on every layout in case a view that was detached 723 // enqueued an action after being detached 724 getRunQueue().executeActions(attachInfo.mHandler); 725 726 if (mFirst) { 727 host.fitSystemWindows(mAttachInfo.mContentInsets); 728 // make sure touch mode code executes by setting cached value 729 // to opposite of the added touch mode. 730 mAttachInfo.mInTouchMode = !mAddedTouchMode; 731 ensureTouchModeLocally(mAddedTouchMode); 732 } else { 733 if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) { 734 mAttachInfo.mContentInsets.set(mPendingContentInsets); 735 host.fitSystemWindows(mAttachInfo.mContentInsets); 736 insetsChanged = true; 737 if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " 738 + mAttachInfo.mContentInsets); 739 } 740 if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) { 741 mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); 742 if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " 743 + mAttachInfo.mVisibleInsets); 744 } 745 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT 746 || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 747 windowResizesToFitContent = true; 748 749 DisplayMetrics packageMetrics = 750 mView.getContext().getResources().getDisplayMetrics(); 751 desiredWindowWidth = packageMetrics.widthPixels; 752 desiredWindowHeight = packageMetrics.heightPixels; 753 } 754 } 755 756 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); 757 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); 758 759 // Ask host how big it wants to be 760 if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot", 761 "Measuring " + host + " in display " + desiredWindowWidth 762 + "x" + desiredWindowHeight + "..."); 763 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 764 765 if (DBG) { 766 System.out.println("======================================"); 767 System.out.println("performTraversals -- after measure"); 768 host.debug(); 769 } 770 } 771 772 if (attachInfo.mRecomputeGlobalAttributes) { 773 //Log.i(TAG, "Computing screen on!"); 774 attachInfo.mRecomputeGlobalAttributes = false; 775 boolean oldVal = attachInfo.mKeepScreenOn; 776 attachInfo.mKeepScreenOn = false; 777 host.dispatchCollectViewAttributes(0); 778 if (attachInfo.mKeepScreenOn != oldVal) { 779 params = lp; 780 //Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn); 781 } 782 } 783 784 if (mFirst || attachInfo.mViewVisibilityChanged) { 785 attachInfo.mViewVisibilityChanged = false; 786 int resizeMode = mSoftInputMode & 787 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 788 // If we are in auto resize mode, then we need to determine 789 // what mode to use now. 790 if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { 791 final int N = attachInfo.mScrollContainers.size(); 792 for (int i=0; i<N; i++) { 793 if (attachInfo.mScrollContainers.get(i).isShown()) { 794 resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 795 } 796 } 797 if (resizeMode == 0) { 798 resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; 799 } 800 if ((lp.softInputMode & 801 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) { 802 lp.softInputMode = (lp.softInputMode & 803 ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) | 804 resizeMode; 805 params = lp; 806 } 807 } 808 } 809 810 if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { 811 if (!PixelFormat.formatHasAlpha(params.format)) { 812 params.format = PixelFormat.TRANSLUCENT; 813 } 814 } 815 816 boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent 817 && (mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight); 818 819 final boolean computesInternalInsets = 820 attachInfo.mTreeObserver.hasComputeInternalInsetsListeners(); 821 boolean insetsPending = false; 822 int relayoutResult = 0; 823 if (mFirst || windowShouldResize || insetsChanged 824 || viewVisibilityChanged || params != null) { 825 826 if (viewVisibility == View.VISIBLE) { 827 // If this window is giving internal insets to the window 828 // manager, and it is being added or changing its visibility, 829 // then we want to first give the window manager "fake" 830 // insets to cause it to effectively ignore the content of 831 // the window during layout. This avoids it briefly causing 832 // other windows to resize/move based on the raw frame of the 833 // window, waiting until we can finish laying out this window 834 // and get back to the window manager with the ultimately 835 // computed insets. 836 insetsPending = computesInternalInsets 837 && (mFirst || viewVisibilityChanged); 838 839 if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) { 840 if (params == null) { 841 params = mWindowAttributes; 842 } 843 mGlWanted = true; 844 } 845 } 846 847 boolean initialized = false; 848 boolean contentInsetsChanged = false; 849 boolean visibleInsetsChanged; 850 try { 851 boolean hadSurface = mSurface.isValid(); 852 int fl = 0; 853 if (params != null) { 854 fl = params.flags; 855 if (attachInfo.mKeepScreenOn) { 856 params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 857 } 858 } 859 if (DEBUG_LAYOUT) { 860 Log.i(TAG, "host=w:" + host.mMeasuredWidth + ", h:" + 861 host.mMeasuredHeight + ", params=" + params); 862 } 863 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); 864 865 if (params != null) { 866 params.flags = fl; 867 } 868 869 if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() 870 + " content=" + mPendingContentInsets.toShortString() 871 + " visible=" + mPendingVisibleInsets.toShortString() 872 + " surface=" + mSurface); 873 874 contentInsetsChanged = !mPendingContentInsets.equals( 875 mAttachInfo.mContentInsets); 876 visibleInsetsChanged = !mPendingVisibleInsets.equals( 877 mAttachInfo.mVisibleInsets); 878 if (contentInsetsChanged) { 879 mAttachInfo.mContentInsets.set(mPendingContentInsets); 880 host.fitSystemWindows(mAttachInfo.mContentInsets); 881 if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " 882 + mAttachInfo.mContentInsets); 883 } 884 if (visibleInsetsChanged) { 885 mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); 886 if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " 887 + mAttachInfo.mVisibleInsets); 888 } 889 890 if (!hadSurface) { 891 if (mSurface.isValid()) { 892 // If we are creating a new surface, then we need to 893 // completely redraw it. Also, when we get to the 894 // point of drawing it we will hold off and schedule 895 // a new traversal instead. This is so we can tell the 896 // window manager about all of the windows being displayed 897 // before actually drawing them, so it can display then 898 // all at once. 899 newSurface = true; 900 fullRedrawNeeded = true; 901 mPreviousTransparentRegion.setEmpty(); 902 903 if (mGlWanted && !mUseGL) { 904 initializeGL(); 905 initialized = mGlCanvas != null; 906 } 907 } 908 } else if (!mSurface.isValid()) { 909 // If the surface has been removed, then reset the scroll 910 // positions. 911 mLastScrolledFocus = null; 912 mScrollY = mCurScrollY = 0; 913 if (mScroller != null) { 914 mScroller.abortAnimation(); 915 } 916 } 917 } catch (RemoteException e) { 918 } 919 if (DEBUG_ORIENTATION) Log.v( 920 "ViewRoot", "Relayout returned: frame=" + frame + ", surface=" + mSurface); 921 922 attachInfo.mWindowLeft = frame.left; 923 attachInfo.mWindowTop = frame.top; 924 925 // !!FIXME!! This next section handles the case where we did not get the 926 // window size we asked for. We should avoid this by getting a maximum size from 927 // the window session beforehand. 928 mWidth = frame.width(); 929 mHeight = frame.height(); 930 931 if (initialized) { 932 mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f), 933 (int) (mHeight * appScale + 0.5f)); 934 } 935 936 boolean focusChangedDueToTouchMode = ensureTouchModeLocally( 937 (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0); 938 if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth 939 || mHeight != host.mMeasuredHeight || contentInsetsChanged) { 940 childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 941 childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 942 943 if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" 944 + mWidth + " measuredWidth=" + host.mMeasuredWidth 945 + " mHeight=" + mHeight 946 + " measuredHeight" + host.mMeasuredHeight 947 + " coveredInsetsChanged=" + contentInsetsChanged); 948 949 // Ask host how big it wants to be 950 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 951 952 // Implementation of weights from WindowManager.LayoutParams 953 // We just grow the dimensions as needed and re-measure if 954 // needs be 955 int width = host.mMeasuredWidth; 956 int height = host.mMeasuredHeight; 957 boolean measureAgain = false; 958 959 if (lp.horizontalWeight > 0.0f) { 960 width += (int) ((mWidth - width) * lp.horizontalWeight); 961 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, 962 MeasureSpec.EXACTLY); 963 measureAgain = true; 964 } 965 if (lp.verticalWeight > 0.0f) { 966 height += (int) ((mHeight - height) * lp.verticalWeight); 967 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, 968 MeasureSpec.EXACTLY); 969 measureAgain = true; 970 } 971 972 if (measureAgain) { 973 if (DEBUG_LAYOUT) Log.v(TAG, 974 "And hey let's measure once more: width=" + width 975 + " height=" + height); 976 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 977 } 978 979 mLayoutRequested = true; 980 } 981 } 982 983 final boolean didLayout = mLayoutRequested; 984 boolean triggerGlobalLayoutListener = didLayout 985 || attachInfo.mRecomputeGlobalAttributes; 986 if (didLayout) { 987 mLayoutRequested = false; 988 mScrollMayChange = true; 989 if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v( 990 "ViewRoot", "Laying out " + host + " to (" + 991 host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")"); 992 long startTime = 0L; 993 if (Config.DEBUG && ViewDebug.profileLayout) { 994 startTime = SystemClock.elapsedRealtime(); 995 } 996 host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); 997 998 if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { 999 if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) { 1000 throw new IllegalStateException("The view hierarchy is an inconsistent state," 1001 + "please refer to the logs with the tag " 1002 + ViewDebug.CONSISTENCY_LOG_TAG + " for more infomation."); 1003 } 1004 } 1005 1006 if (Config.DEBUG && ViewDebug.profileLayout) { 1007 EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime); 1008 } 1009 1010 // By this point all views have been sized and positionned 1011 // We can compute the transparent area 1012 1013 if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { 1014 // start out transparent 1015 // TODO: AVOID THAT CALL BY CACHING THE RESULT? 1016 host.getLocationInWindow(mTmpLocation); 1017 mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1], 1018 mTmpLocation[0] + host.mRight - host.mLeft, 1019 mTmpLocation[1] + host.mBottom - host.mTop); 1020 1021 host.gatherTransparentRegion(mTransparentRegion); 1022 if (mTranslator != null) { 1023 mTranslator.translateRegionInWindowToScreen(mTransparentRegion); 1024 } 1025 1026 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { 1027 mPreviousTransparentRegion.set(mTransparentRegion); 1028 // reconfigure window manager 1029 try { 1030 sWindowSession.setTransparentRegion(mWindow, mTransparentRegion); 1031 } catch (RemoteException e) { 1032 } 1033 } 1034 } 1035 1036 if (DBG) { 1037 System.out.println("======================================"); 1038 System.out.println("performTraversals -- after setFrame"); 1039 host.debug(); 1040 } 1041 } 1042 1043 if (triggerGlobalLayoutListener) { 1044 attachInfo.mRecomputeGlobalAttributes = false; 1045 attachInfo.mTreeObserver.dispatchOnGlobalLayout(); 1046 } 1047 1048 if (computesInternalInsets) { 1049 ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets; 1050 final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets; 1051 final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets; 1052 givenContent.left = givenContent.top = givenContent.right 1053 = givenContent.bottom = givenVisible.left = givenVisible.top 1054 = givenVisible.right = givenVisible.bottom = 0; 1055 attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); 1056 Rect contentInsets = insets.contentInsets; 1057 Rect visibleInsets = insets.visibleInsets; 1058 if (mTranslator != null) { 1059 contentInsets = mTranslator.getTranslatedContentInsets(contentInsets); 1060 visibleInsets = mTranslator.getTranslatedVisbileInsets(visibleInsets); 1061 } 1062 if (insetsPending || !mLastGivenInsets.equals(insets)) { 1063 mLastGivenInsets.set(insets); 1064 try { 1065 sWindowSession.setInsets(mWindow, insets.mTouchableInsets, 1066 contentInsets, visibleInsets); 1067 } catch (RemoteException e) { 1068 } 1069 } 1070 } 1071 1072 if (mFirst) { 1073 // handle first focus request 1074 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()=" 1075 + mView.hasFocus()); 1076 if (mView != null) { 1077 if (!mView.hasFocus()) { 1078 mView.requestFocus(View.FOCUS_FORWARD); 1079 mFocusedView = mRealFocusedView = mView.findFocus(); 1080 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view=" 1081 + mFocusedView); 1082 } else { 1083 mRealFocusedView = mView.findFocus(); 1084 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view=" 1085 + mRealFocusedView); 1086 } 1087 } 1088 } 1089 1090 mFirst = false; 1091 mWillDrawSoon = false; 1092 mNewSurfaceNeeded = false; 1093 mViewVisibility = viewVisibility; 1094 1095 if (mAttachInfo.mHasWindowFocus) { 1096 final boolean imTarget = WindowManager.LayoutParams 1097 .mayUseInputMethod(mWindowAttributes.flags); 1098 if (imTarget != mLastWasImTarget) { 1099 mLastWasImTarget = imTarget; 1100 InputMethodManager imm = InputMethodManager.peekInstance(); 1101 if (imm != null && imTarget) { 1102 imm.startGettingWindowFocus(mView); 1103 imm.onWindowFocus(mView, mView.findFocus(), 1104 mWindowAttributes.softInputMode, 1105 !mHasHadWindowFocus, mWindowAttributes.flags); 1106 } 1107 } 1108 } 1109 1110 boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw(); 1111 1112 if (!cancelDraw && !newSurface) { 1113 mFullRedrawNeeded = false; 1114 draw(fullRedrawNeeded); 1115 1116 if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0 1117 || mReportNextDraw) { 1118 if (LOCAL_LOGV) { 1119 Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle()); 1120 } 1121 mReportNextDraw = false; 1122 try { 1123 sWindowSession.finishDrawing(mWindow); 1124 } catch (RemoteException e) { 1125 } 1126 } 1127 } else { 1128 // We were supposed to report when we are done drawing. Since we canceled the 1129 // draw, remember it here. 1130 if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { 1131 mReportNextDraw = true; 1132 } 1133 if (fullRedrawNeeded) { 1134 mFullRedrawNeeded = true; 1135 } 1136 // Try again 1137 scheduleTraversals(); 1138 } 1139 } 1140 1141 public void requestTransparentRegion(View child) { 1142 // the test below should not fail unless someone is messing with us 1143 checkThread(); 1144 if (mView == child) { 1145 mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS; 1146 // Need to make sure we re-evaluate the window attributes next 1147 // time around, to ensure the window has the correct format. 1148 mWindowAttributesChanged = true; 1149 } 1150 } 1151 1152 /** 1153 * Figures out the measure spec for the root view in a window based on it's 1154 * layout params. 1155 * 1156 * @param windowSize 1157 * The available width or height of the window 1158 * 1159 * @param rootDimension 1160 * The layout params for one dimension (width or height) of the 1161 * window. 1162 * 1163 * @return The measure spec to use to measure the root view. 1164 */ 1165 private int getRootMeasureSpec(int windowSize, int rootDimension) { 1166 int measureSpec; 1167 switch (rootDimension) { 1168 1169 case ViewGroup.LayoutParams.FILL_PARENT: 1170 // Window can't resize. Force root view to be windowSize. 1171 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 1172 break; 1173 case ViewGroup.LayoutParams.WRAP_CONTENT: 1174 // Window can resize. Set max size for root view. 1175 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 1176 break; 1177 default: 1178 // Window wants to be an exact size. Force root view to be that size. 1179 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 1180 break; 1181 } 1182 return measureSpec; 1183 } 1184 1185 private void draw(boolean fullRedrawNeeded) { 1186 Surface surface = mSurface; 1187 if (surface == null || !surface.isValid()) { 1188 return; 1189 } 1190 1191 scrollToRectOrFocus(null, false); 1192 1193 if (mAttachInfo.mViewScrollChanged) { 1194 mAttachInfo.mViewScrollChanged = false; 1195 mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); 1196 } 1197 1198 int yoff; 1199 final boolean scrolling = mScroller != null && mScroller.computeScrollOffset(); 1200 if (scrolling) { 1201 yoff = mScroller.getCurrY(); 1202 } else { 1203 yoff = mScrollY; 1204 } 1205 if (mCurScrollY != yoff) { 1206 mCurScrollY = yoff; 1207 fullRedrawNeeded = true; 1208 } 1209 float appScale = mAttachInfo.mApplicationScale; 1210 boolean scalingRequired = mAttachInfo.mScalingRequired; 1211 1212 Rect dirty = mDirty; 1213 if (mUseGL) { 1214 if (!dirty.isEmpty()) { 1215 Canvas canvas = mGlCanvas; 1216 if (mGL != null && canvas != null) { 1217 mGL.glDisable(GL_SCISSOR_TEST); 1218 mGL.glClearColor(0, 0, 0, 0); 1219 mGL.glClear(GL_COLOR_BUFFER_BIT); 1220 mGL.glEnable(GL_SCISSOR_TEST); 1221 1222 mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); 1223 mAttachInfo.mIgnoreDirtyState = true; 1224 mView.mPrivateFlags |= View.DRAWN; 1225 1226 int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); 1227 try { 1228 canvas.translate(0, -yoff); 1229 if (mTranslator != null) { 1230 mTranslator.translateCanvas(canvas); 1231 } 1232 canvas.setScreenDensity(scalingRequired 1233 ? DisplayMetrics.DENSITY_DEVICE : 0); 1234 mView.draw(canvas); 1235 if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { 1236 mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); 1237 } 1238 } finally { 1239 canvas.restoreToCount(saveCount); 1240 } 1241 1242 mAttachInfo.mIgnoreDirtyState = false; 1243 1244 mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); 1245 checkEglErrors(); 1246 1247 if (Config.DEBUG && ViewDebug.showFps) { 1248 int now = (int)SystemClock.elapsedRealtime(); 1249 if (sDrawTime != 0) { 1250 nativeShowFPS(canvas, now - sDrawTime); 1251 } 1252 sDrawTime = now; 1253 } 1254 } 1255 } 1256 if (scrolling) { 1257 mFullRedrawNeeded = true; 1258 scheduleTraversals(); 1259 } 1260 return; 1261 } 1262 1263 if (fullRedrawNeeded) { 1264 mAttachInfo.mIgnoreDirtyState = true; 1265 dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); 1266 } 1267 1268 if (DEBUG_ORIENTATION || DEBUG_DRAW) { 1269 Log.v("ViewRoot", "Draw " + mView + "/" 1270 + mWindowAttributes.getTitle() 1271 + ": dirty={" + dirty.left + "," + dirty.top 1272 + "," + dirty.right + "," + dirty.bottom + "} surface=" 1273 + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" + 1274 appScale + ", width=" + mWidth + ", height=" + mHeight); 1275 } 1276 1277 Canvas canvas; 1278 try { 1279 int left = dirty.left; 1280 int top = dirty.top; 1281 int right = dirty.right; 1282 int bottom = dirty.bottom; 1283 canvas = surface.lockCanvas(dirty); 1284 1285 if (left != dirty.left || top != dirty.top || right != dirty.right || 1286 bottom != dirty.bottom) { 1287 mAttachInfo.mIgnoreDirtyState = true; 1288 } 1289 1290 // TODO: Do this in native 1291 canvas.setDensity(mDensity); 1292 } catch (Surface.OutOfResourcesException e) { 1293 Log.e("ViewRoot", "OutOfResourcesException locking surface", e); 1294 // TODO: we should ask the window manager to do something! 1295 // for now we just do nothing 1296 return; 1297 } catch (IllegalArgumentException e) { 1298 Log.e("ViewRoot", "IllegalArgumentException locking surface", e); 1299 // TODO: we should ask the window manager to do something! 1300 // for now we just do nothing 1301 return; 1302 } 1303 1304 try { 1305 if (!dirty.isEmpty() || mIsAnimating) { 1306 long startTime = 0L; 1307 1308 if (DEBUG_ORIENTATION || DEBUG_DRAW) { 1309 Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" 1310 + canvas.getWidth() + ", h=" + canvas.getHeight()); 1311 //canvas.drawARGB(255, 255, 0, 0); 1312 } 1313 1314 if (Config.DEBUG && ViewDebug.profileDrawing) { 1315 startTime = SystemClock.elapsedRealtime(); 1316 } 1317 1318 // If this bitmap's format includes an alpha channel, we 1319 // need to clear it before drawing so that the child will 1320 // properly re-composite its drawing on a transparent 1321 // background. This automatically respects the clip/dirty region 1322 // or 1323 // If we are applying an offset, we need to clear the area 1324 // where the offset doesn't appear to avoid having garbage 1325 // left in the blank areas. 1326 if (!canvas.isOpaque() || yoff != 0) { 1327 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 1328 } 1329 1330 dirty.setEmpty(); 1331 mIsAnimating = false; 1332 mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); 1333 mView.mPrivateFlags |= View.DRAWN; 1334 1335 if (DEBUG_DRAW) { 1336 Context cxt = mView.getContext(); 1337 Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + 1338 ", metrics=" + cxt.getResources().getDisplayMetrics() + 1339 ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); 1340 } 1341 int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); 1342 try { 1343 canvas.translate(0, -yoff); 1344 if (mTranslator != null) { 1345 mTranslator.translateCanvas(canvas); 1346 } 1347 canvas.setScreenDensity(scalingRequired 1348 ? DisplayMetrics.DENSITY_DEVICE : 0); 1349 mView.draw(canvas); 1350 } finally { 1351 mAttachInfo.mIgnoreDirtyState = false; 1352 canvas.restoreToCount(saveCount); 1353 } 1354 1355 if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { 1356 mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); 1357 } 1358 1359 if (Config.DEBUG && ViewDebug.showFps) { 1360 int now = (int)SystemClock.elapsedRealtime(); 1361 if (sDrawTime != 0) { 1362 nativeShowFPS(canvas, now - sDrawTime); 1363 } 1364 sDrawTime = now; 1365 } 1366 1367 if (Config.DEBUG && ViewDebug.profileDrawing) { 1368 EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); 1369 } 1370 } 1371 1372 } finally { 1373 surface.unlockCanvasAndPost(canvas); 1374 } 1375 1376 if (LOCAL_LOGV) { 1377 Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost"); 1378 } 1379 1380 if (scrolling) { 1381 mFullRedrawNeeded = true; 1382 scheduleTraversals(); 1383 } 1384 } 1385 1386 boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { 1387 final View.AttachInfo attachInfo = mAttachInfo; 1388 final Rect ci = attachInfo.mContentInsets; 1389 final Rect vi = attachInfo.mVisibleInsets; 1390 int scrollY = 0; 1391 boolean handled = false; 1392 1393 if (vi.left > ci.left || vi.top > ci.top 1394 || vi.right > ci.right || vi.bottom > ci.bottom) { 1395 // We'll assume that we aren't going to change the scroll 1396 // offset, since we want to avoid that unless it is actually 1397 // going to make the focus visible... otherwise we scroll 1398 // all over the place. 1399 scrollY = mScrollY; 1400 // We can be called for two different situations: during a draw, 1401 // to update the scroll position if the focus has changed (in which 1402 // case 'rectangle' is null), or in response to a 1403 // requestChildRectangleOnScreen() call (in which case 'rectangle' 1404 // is non-null and we just want to scroll to whatever that 1405 // rectangle is). 1406 View focus = mRealFocusedView; 1407 1408 // When in touch mode, focus points to the previously focused view, 1409 // which may have been removed from the view hierarchy. The following 1410 // line checks whether the view is still in our hierarchy. 1411 if (focus == null || focus.mAttachInfo != mAttachInfo) { 1412 mRealFocusedView = null; 1413 return false; 1414 } 1415 1416 if (focus != mLastScrolledFocus) { 1417 // If the focus has changed, then ignore any requests to scroll 1418 // to a rectangle; first we want to make sure the entire focus 1419 // view is visible. 1420 rectangle = null; 1421 } 1422 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus 1423 + " rectangle=" + rectangle + " ci=" + ci 1424 + " vi=" + vi); 1425 if (focus == mLastScrolledFocus && !mScrollMayChange 1426 && rectangle == null) { 1427 // Optimization: if the focus hasn't changed since last 1428 // time, and no layout has happened, then just leave things 1429 // as they are. 1430 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y=" 1431 + mScrollY + " vi=" + vi.toShortString()); 1432 } else if (focus != null) { 1433 // We need to determine if the currently focused view is 1434 // within the visible part of the window and, if not, apply 1435 // a pan so it can be seen. 1436 mLastScrolledFocus = focus; 1437 mScrollMayChange = false; 1438 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?"); 1439 // Try to find the rectangle from the focus view. 1440 if (focus.getGlobalVisibleRect(mVisRect, null)) { 1441 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w=" 1442 + mView.getWidth() + " h=" + mView.getHeight() 1443 + " ci=" + ci.toShortString() 1444 + " vi=" + vi.toShortString()); 1445 if (rectangle == null) { 1446 focus.getFocusedRect(mTempRect); 1447 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus 1448 + ": focusRect=" + mTempRect.toShortString()); 1449 ((ViewGroup) mView).offsetDescendantRectToMyCoords( 1450 focus, mTempRect); 1451 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1452 "Focus in window: focusRect=" 1453 + mTempRect.toShortString() 1454 + " visRect=" + mVisRect.toShortString()); 1455 } else { 1456 mTempRect.set(rectangle); 1457 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1458 "Request scroll to rect: " 1459 + mTempRect.toShortString() 1460 + " visRect=" + mVisRect.toShortString()); 1461 } 1462 if (mTempRect.intersect(mVisRect)) { 1463 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1464 "Focus window visible rect: " 1465 + mTempRect.toShortString()); 1466 if (mTempRect.height() > 1467 (mView.getHeight()-vi.top-vi.bottom)) { 1468 // If the focus simply is not going to fit, then 1469 // best is probably just to leave things as-is. 1470 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1471 "Too tall; leaving scrollY=" + scrollY); 1472 } else if ((mTempRect.top-scrollY) < vi.top) { 1473 scrollY -= vi.top - (mTempRect.top-scrollY); 1474 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1475 "Top covered; scrollY=" + scrollY); 1476 } else if ((mTempRect.bottom-scrollY) 1477 > (mView.getHeight()-vi.bottom)) { 1478 scrollY += (mTempRect.bottom-scrollY) 1479 - (mView.getHeight()-vi.bottom); 1480 if (DEBUG_INPUT_RESIZE) Log.v(TAG, 1481 "Bottom covered; scrollY=" + scrollY); 1482 } 1483 handled = true; 1484 } 1485 } 1486 } 1487 } 1488 1489 if (scrollY != mScrollY) { 1490 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old=" 1491 + mScrollY + " , new=" + scrollY); 1492 if (!immediate) { 1493 if (mScroller == null) { 1494 mScroller = new Scroller(mView.getContext()); 1495 } 1496 mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY); 1497 } else if (mScroller != null) { 1498 mScroller.abortAnimation(); 1499 } 1500 mScrollY = scrollY; 1501 } 1502 1503 return handled; 1504 } 1505 1506 public void requestChildFocus(View child, View focused) { 1507 checkThread(); 1508 if (mFocusedView != focused) { 1509 mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused); 1510 scheduleTraversals(); 1511 } 1512 mFocusedView = mRealFocusedView = focused; 1513 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now " 1514 + mFocusedView); 1515 } 1516 1517 public void clearChildFocus(View child) { 1518 checkThread(); 1519 1520 View oldFocus = mFocusedView; 1521 1522 if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus"); 1523 mFocusedView = mRealFocusedView = null; 1524 if (mView != null && !mView.hasFocus()) { 1525 // If a view gets the focus, the listener will be invoked from requestChildFocus() 1526 if (!mView.requestFocus(View.FOCUS_FORWARD)) { 1527 mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); 1528 } 1529 } else if (oldFocus != null) { 1530 mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); 1531 } 1532 } 1533 1534 1535 public void focusableViewAvailable(View v) { 1536 checkThread(); 1537 1538 if (mView != null && !mView.hasFocus()) { 1539 v.requestFocus(); 1540 } else { 1541 // the one case where will transfer focus away from the current one 1542 // is if the current view is a view group that prefers to give focus 1543 // to its children first AND the view is a descendant of it. 1544 mFocusedView = mView.findFocus(); 1545 boolean descendantsHaveDibsOnFocus = 1546 (mFocusedView instanceof ViewGroup) && 1547 (((ViewGroup) mFocusedView).getDescendantFocusability() == 1548 ViewGroup.FOCUS_AFTER_DESCENDANTS); 1549 if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) { 1550 // If a view gets the focus, the listener will be invoked from requestChildFocus() 1551 v.requestFocus(); 1552 } 1553 } 1554 } 1555 1556 public void recomputeViewAttributes(View child) { 1557 checkThread(); 1558 if (mView == child) { 1559 mAttachInfo.mRecomputeGlobalAttributes = true; 1560 if (!mWillDrawSoon) { 1561 scheduleTraversals(); 1562 } 1563 } 1564 } 1565 1566 void dispatchDetachedFromWindow() { 1567 if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface); 1568 1569 if (mView != null) { 1570 mView.dispatchDetachedFromWindow(); 1571 } 1572 1573 mView = null; 1574 mAttachInfo.mRootView = null; 1575 mAttachInfo.mSurface = null; 1576 1577 if (mUseGL) { 1578 destroyGL(); 1579 } 1580 mSurface.release(); 1581 1582 try { 1583 sWindowSession.remove(mWindow); 1584 } catch (RemoteException e) { 1585 } 1586 } 1587 1588 /** 1589 * Return true if child is an ancestor of parent, (or equal to the parent). 1590 */ 1591 private static boolean isViewDescendantOf(View child, View parent) { 1592 if (child == parent) { 1593 return true; 1594 } 1595 1596 final ViewParent theParent = child.getParent(); 1597 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); 1598 } 1599 1600 1601 public final static int DO_TRAVERSAL = 1000; 1602 public final static int DIE = 1001; 1603 public final static int RESIZED = 1002; 1604 public final static int RESIZED_REPORT = 1003; 1605 public final static int WINDOW_FOCUS_CHANGED = 1004; 1606 public final static int DISPATCH_KEY = 1005; 1607 public final static int DISPATCH_POINTER = 1006; 1608 public final static int DISPATCH_TRACKBALL = 1007; 1609 public final static int DISPATCH_APP_VISIBILITY = 1008; 1610 public final static int DISPATCH_GET_NEW_SURFACE = 1009; 1611 public final static int FINISHED_EVENT = 1010; 1612 public final static int DISPATCH_KEY_FROM_IME = 1011; 1613 public final static int FINISH_INPUT_CONNECTION = 1012; 1614 public final static int CHECK_FOCUS = 1013; 1615 public final static int CLOSE_SYSTEM_DIALOGS = 1014; 1616 1617 @Override 1618 public void handleMessage(Message msg) { 1619 switch (msg.what) { 1620 case View.AttachInfo.INVALIDATE_MSG: 1621 ((View) msg.obj).invalidate(); 1622 break; 1623 case View.AttachInfo.INVALIDATE_RECT_MSG: 1624 final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; 1625 info.target.invalidate(info.left, info.top, info.right, info.bottom); 1626 info.release(); 1627 break; 1628 case DO_TRAVERSAL: 1629 if (mProfile) { 1630 Debug.startMethodTracing("ViewRoot"); 1631 } 1632 1633 performTraversals(); 1634 1635 if (mProfile) { 1636 Debug.stopMethodTracing(); 1637 mProfile = false; 1638 } 1639 break; 1640 case FINISHED_EVENT: 1641 handleFinishedEvent(msg.arg1, msg.arg2 != 0); 1642 break; 1643 case DISPATCH_KEY: 1644 if (LOCAL_LOGV) Log.v( 1645 "ViewRoot", "Dispatching key " 1646 + msg.obj + " to " + mView); 1647 deliverKeyEvent((KeyEvent)msg.obj, true); 1648 break; 1649 case DISPATCH_POINTER: { 1650 MotionEvent event = (MotionEvent)msg.obj; 1651 boolean callWhenDone = msg.arg1 != 0; 1652 1653 if (event == null) { 1654 try { 1655 long timeBeforeGettingEvents; 1656 if (MEASURE_LATENCY) { 1657 timeBeforeGettingEvents = System.nanoTime(); 1658 } 1659 1660 event = sWindowSession.getPendingPointerMove(mWindow); 1661 1662 if (MEASURE_LATENCY && event != null) { 1663 lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano()); 1664 lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano()); 1665 } 1666 } catch (RemoteException e) { 1667 } 1668 callWhenDone = false; 1669 } 1670 if (event != null && mTranslator != null) { 1671 mTranslator.translateEventInScreenToAppWindow(event); 1672 } 1673 try { 1674 boolean handled; 1675 if (mView != null && mAdded && event != null) { 1676 1677 // enter touch mode on the down 1678 boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; 1679 if (isDown) { 1680 ensureTouchMode(true); 1681 } 1682 if(Config.LOGV) { 1683 captureMotionLog("captureDispatchPointer", event); 1684 } 1685 if (mCurScrollY != 0) { 1686 event.offsetLocation(0, mCurScrollY); 1687 } 1688 if (MEASURE_LATENCY) { 1689 lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); 1690 } 1691 handled = mView.dispatchTouchEvent(event); 1692 if (MEASURE_LATENCY) { 1693 lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); 1694 } 1695 if (!handled && isDown) { 1696 int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); 1697 1698 final int edgeFlags = event.getEdgeFlags(); 1699 int direction = View.FOCUS_UP; 1700 int x = (int)event.getX(); 1701 int y = (int)event.getY(); 1702 final int[] deltas = new int[2]; 1703 1704 if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { 1705 direction = View.FOCUS_DOWN; 1706 if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { 1707 deltas[0] = edgeSlop; 1708 x += edgeSlop; 1709 } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { 1710 deltas[0] = -edgeSlop; 1711 x -= edgeSlop; 1712 } 1713 } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { 1714 direction = View.FOCUS_UP; 1715 if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { 1716 deltas[0] = edgeSlop; 1717 x += edgeSlop; 1718 } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { 1719 deltas[0] = -edgeSlop; 1720 x -= edgeSlop; 1721 } 1722 } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { 1723 direction = View.FOCUS_RIGHT; 1724 } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { 1725 direction = View.FOCUS_LEFT; 1726 } 1727 1728 if (edgeFlags != 0 && mView instanceof ViewGroup) { 1729 View nearest = FocusFinder.getInstance().findNearestTouchable( 1730 ((ViewGroup) mView), x, y, direction, deltas); 1731 if (nearest != null) { 1732 event.offsetLocation(deltas[0], deltas[1]); 1733 event.setEdgeFlags(0); 1734 mView.dispatchTouchEvent(event); 1735 } 1736 } 1737 } 1738 } 1739 } finally { 1740 if (callWhenDone) { 1741 try { 1742 sWindowSession.finishKey(mWindow); 1743 } catch (RemoteException e) { 1744 } 1745 } 1746 if (event != null) { 1747 event.recycle(); 1748 } 1749 if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); 1750 // Let the exception fall through -- the looper will catch 1751 // it and take care of the bad app for us. 1752 } 1753 } break; 1754 case DISPATCH_TRACKBALL: 1755 deliverTrackballEvent((MotionEvent)msg.obj, msg.arg1 != 0); 1756 break; 1757 case DISPATCH_APP_VISIBILITY: 1758 handleAppVisibility(msg.arg1 != 0); 1759 break; 1760 case DISPATCH_GET_NEW_SURFACE: 1761 handleGetNewSurface(); 1762 break; 1763 case RESIZED: 1764 Rect coveredInsets = ((Rect[])msg.obj)[0]; 1765 Rect visibleInsets = ((Rect[])msg.obj)[1]; 1766 1767 if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2 1768 && mPendingContentInsets.equals(coveredInsets) 1769 && mPendingVisibleInsets.equals(visibleInsets)) { 1770 break; 1771 } 1772 // fall through... 1773 case RESIZED_REPORT: 1774 if (mAdded) { 1775 mWinFrame.left = 0; 1776 mWinFrame.right = msg.arg1; 1777 mWinFrame.top = 0; 1778 mWinFrame.bottom = msg.arg2; 1779 mPendingContentInsets.set(((Rect[])msg.obj)[0]); 1780 mPendingVisibleInsets.set(((Rect[])msg.obj)[1]); 1781 if (msg.what == RESIZED_REPORT) { 1782 mReportNextDraw = true; 1783 } 1784 requestLayout(); 1785 } 1786 break; 1787 case WINDOW_FOCUS_CHANGED: { 1788 if (mAdded) { 1789 boolean hasWindowFocus = msg.arg1 != 0; 1790 mAttachInfo.mHasWindowFocus = hasWindowFocus; 1791 if (hasWindowFocus) { 1792 boolean inTouchMode = msg.arg2 != 0; 1793 ensureTouchModeLocally(inTouchMode); 1794 1795 if (mGlWanted) { 1796 checkEglErrors(); 1797 // we lost the gl context, so recreate it. 1798 if (mGlWanted && !mUseGL) { 1799 initializeGL(); 1800 if (mGlCanvas != null) { 1801 float appScale = mAttachInfo.mApplicationScale; 1802 mGlCanvas.setViewport( 1803 (int) (mWidth * appScale + 0.5f), 1804 (int) (mHeight * appScale + 0.5f)); 1805 } 1806 } 1807 } 1808 } 1809 1810 mLastWasImTarget = WindowManager.LayoutParams 1811 .mayUseInputMethod(mWindowAttributes.flags); 1812 1813 InputMethodManager imm = InputMethodManager.peekInstance(); 1814 if (mView != null) { 1815 if (hasWindowFocus && imm != null && mLastWasImTarget) { 1816 imm.startGettingWindowFocus(mView); 1817 } 1818 mAttachInfo.mKeyDispatchState.reset(); 1819 mView.dispatchWindowFocusChanged(hasWindowFocus); 1820 } 1821 1822 // Note: must be done after the focus change callbacks, 1823 // so all of the view state is set up correctly. 1824 if (hasWindowFocus) { 1825 if (imm != null && mLastWasImTarget) { 1826 imm.onWindowFocus(mView, mView.findFocus(), 1827 mWindowAttributes.softInputMode, 1828 !mHasHadWindowFocus, mWindowAttributes.flags); 1829 } 1830 // Clear the forward bit. We can just do this directly, since 1831 // the window manager doesn't care about it. 1832 mWindowAttributes.softInputMode &= 1833 ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; 1834 ((WindowManager.LayoutParams)mView.getLayoutParams()) 1835 .softInputMode &= 1836 ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; 1837 mHasHadWindowFocus = true; 1838 } 1839 1840 if (hasWindowFocus && mView != null) { 1841 sendAccessibilityEvents(); 1842 } 1843 } 1844 } break; 1845 case DIE: 1846 doDie(); 1847 break; 1848 case DISPATCH_KEY_FROM_IME: { 1849 if (LOCAL_LOGV) Log.v( 1850 "ViewRoot", "Dispatching key " 1851 + msg.obj + " from IME to " + mView); 1852 KeyEvent event = (KeyEvent)msg.obj; 1853 if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) { 1854 // The IME is trying to say this event is from the 1855 // system! Bad bad bad! 1856 event = KeyEvent.changeFlags(event, 1857 event.getFlags()&~KeyEvent.FLAG_FROM_SYSTEM); 1858 } 1859 deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); 1860 } break; 1861 case FINISH_INPUT_CONNECTION: { 1862 InputMethodManager imm = InputMethodManager.peekInstance(); 1863 if (imm != null) { 1864 imm.reportFinishInputConnection((InputConnection)msg.obj); 1865 } 1866 } break; 1867 case CHECK_FOCUS: { 1868 InputMethodManager imm = InputMethodManager.peekInstance(); 1869 if (imm != null) { 1870 imm.checkFocus(); 1871 } 1872 } break; 1873 case CLOSE_SYSTEM_DIALOGS: { 1874 if (mView != null) { 1875 mView.onCloseSystemDialogs((String)msg.obj); 1876 } 1877 } break; 1878 } 1879 } 1880 1881 /** 1882 * Something in the current window tells us we need to change the touch mode. For 1883 * example, we are not in touch mode, and the user touches the screen. 1884 * 1885 * If the touch mode has changed, tell the window manager, and handle it locally. 1886 * 1887 * @param inTouchMode Whether we want to be in touch mode. 1888 * @return True if the touch mode changed and focus changed was changed as a result 1889 */ 1890 boolean ensureTouchMode(boolean inTouchMode) { 1891 if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current " 1892 + "touch mode is " + mAttachInfo.mInTouchMode); 1893 if (mAttachInfo.mInTouchMode == inTouchMode) return false; 1894 1895 // tell the window manager 1896 try { 1897 sWindowSession.setInTouchMode(inTouchMode); 1898 } catch (RemoteException e) { 1899 throw new RuntimeException(e); 1900 } 1901 1902 // handle the change 1903 return ensureTouchModeLocally(inTouchMode); 1904 } 1905 1906 /** 1907 * Ensure that the touch mode for this window is set, and if it is changing, 1908 * take the appropriate action. 1909 * @param inTouchMode Whether we want to be in touch mode. 1910 * @return True if the touch mode changed and focus changed was changed as a result 1911 */ 1912 private boolean ensureTouchModeLocally(boolean inTouchMode) { 1913 if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current " 1914 + "touch mode is " + mAttachInfo.mInTouchMode); 1915 1916 if (mAttachInfo.mInTouchMode == inTouchMode) return false; 1917 1918 mAttachInfo.mInTouchMode = inTouchMode; 1919 mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode); 1920 1921 return (inTouchMode) ? enterTouchMode() : leaveTouchMode(); 1922 } 1923 1924 private boolean enterTouchMode() { 1925 if (mView != null) { 1926 if (mView.hasFocus()) { 1927 // note: not relying on mFocusedView here because this could 1928 // be when the window is first being added, and mFocused isn't 1929 // set yet. 1930 final View focused = mView.findFocus(); 1931 if (focused != null && !focused.isFocusableInTouchMode()) { 1932 1933 final ViewGroup ancestorToTakeFocus = 1934 findAncestorToTakeFocusInTouchMode(focused); 1935 if (ancestorToTakeFocus != null) { 1936 // there is an ancestor that wants focus after its descendants that 1937 // is focusable in touch mode.. give it focus 1938 return ancestorToTakeFocus.requestFocus(); 1939 } else { 1940 // nothing appropriate to have focus in touch mode, clear it out 1941 mView.unFocus(); 1942 mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null); 1943 mFocusedView = null; 1944 return true; 1945 } 1946 } 1947 } 1948 } 1949 return false; 1950 } 1951 1952 1953 /** 1954 * Find an ancestor of focused that wants focus after its descendants and is 1955 * focusable in touch mode. 1956 * @param focused The currently focused view. 1957 * @return An appropriate view, or null if no such view exists. 1958 */ 1959 private ViewGroup findAncestorToTakeFocusInTouchMode(View focused) { 1960 ViewParent parent = focused.getParent(); 1961 while (parent instanceof ViewGroup) { 1962 final ViewGroup vgParent = (ViewGroup) parent; 1963 if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS 1964 && vgParent.isFocusableInTouchMode()) { 1965 return vgParent; 1966 } 1967 if (vgParent.isRootNamespace()) { 1968 return null; 1969 } else { 1970 parent = vgParent.getParent(); 1971 } 1972 } 1973 return null; 1974 } 1975 1976 private boolean leaveTouchMode() { 1977 if (mView != null) { 1978 if (mView.hasFocus()) { 1979 // i learned the hard way to not trust mFocusedView :) 1980 mFocusedView = mView.findFocus(); 1981 if (!(mFocusedView instanceof ViewGroup)) { 1982 // some view has focus, let it keep it 1983 return false; 1984 } else if (((ViewGroup)mFocusedView).getDescendantFocusability() != 1985 ViewGroup.FOCUS_AFTER_DESCENDANTS) { 1986 // some view group has focus, and doesn't prefer its children 1987 // over itself for focus, so let them keep it. 1988 return false; 1989 } 1990 } 1991 1992 // find the best view to give focus to in this brave new non-touch-mode 1993 // world 1994 final View focused = focusSearch(null, View.FOCUS_DOWN); 1995 if (focused != null) { 1996 return focused.requestFocus(View.FOCUS_DOWN); 1997 } 1998 } 1999 return false; 2000 } 2001 2002 2003 private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) { 2004 if (event == null) { 2005 try { 2006 event = sWindowSession.getPendingTrackballMove(mWindow); 2007 } catch (RemoteException e) { 2008 } 2009 callWhenDone = false; 2010 } 2011 2012 if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event); 2013 2014 boolean handled = false; 2015 try { 2016 if (event == null) { 2017 handled = true; 2018 } else if (mView != null && mAdded) { 2019 handled = mView.dispatchTrackballEvent(event); 2020 if (!handled) { 2021 // we could do something here, like changing the focus 2022 // or something? 2023 } 2024 } 2025 } finally { 2026 if (handled) { 2027 if (callWhenDone) { 2028 try { 2029 sWindowSession.finishKey(mWindow); 2030 } catch (RemoteException e) { 2031 } 2032 } 2033 if (event != null) { 2034 event.recycle(); 2035 } 2036 // If we reach this, we delivered a trackball event to mView and 2037 // mView consumed it. Because we will not translate the trackball 2038 // event into a key event, touch mode will not exit, so we exit 2039 // touch mode here. 2040 ensureTouchMode(false); 2041 //noinspection ReturnInsideFinallyBlock 2042 return; 2043 } 2044 // Let the exception fall through -- the looper will catch 2045 // it and take care of the bad app for us. 2046 } 2047 2048 final TrackballAxis x = mTrackballAxisX; 2049 final TrackballAxis y = mTrackballAxisY; 2050 2051 long curTime = SystemClock.uptimeMillis(); 2052 if ((mLastTrackballTime+MAX_TRACKBALL_DELAY) < curTime) { 2053 // It has been too long since the last movement, 2054 // so restart at the beginning. 2055 x.reset(0); 2056 y.reset(0); 2057 mLastTrackballTime = curTime; 2058 } 2059 2060 try { 2061 final int action = event.getAction(); 2062 final int metastate = event.getMetaState(); 2063 switch (action) { 2064 case MotionEvent.ACTION_DOWN: 2065 x.reset(2); 2066 y.reset(2); 2067 deliverKeyEvent(new KeyEvent(curTime, curTime, 2068 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 2069 0, metastate), false); 2070 break; 2071 case MotionEvent.ACTION_UP: 2072 x.reset(2); 2073 y.reset(2); 2074 deliverKeyEvent(new KeyEvent(curTime, curTime, 2075 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 2076 0, metastate), false); 2077 break; 2078 } 2079 2080 if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" 2081 + x.step + " dir=" + x.dir + " acc=" + x.acceleration 2082 + " move=" + event.getX() 2083 + " / Y=" + y.position + " step=" 2084 + y.step + " dir=" + y.dir + " acc=" + y.acceleration 2085 + " move=" + event.getY()); 2086 final float xOff = x.collect(event.getX(), event.getEventTime(), "X"); 2087 final float yOff = y.collect(event.getY(), event.getEventTime(), "Y"); 2088 2089 // Generate DPAD events based on the trackball movement. 2090 // We pick the axis that has moved the most as the direction of 2091 // the DPAD. When we generate DPAD events for one axis, then the 2092 // other axis is reset -- we don't want to perform DPAD jumps due 2093 // to slight movements in the trackball when making major movements 2094 // along the other axis. 2095 int keycode = 0; 2096 int movement = 0; 2097 float accel = 1; 2098 if (xOff > yOff) { 2099 movement = x.generate((2/event.getXPrecision())); 2100 if (movement != 0) { 2101 keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT 2102 : KeyEvent.KEYCODE_DPAD_LEFT; 2103 accel = x.acceleration; 2104 y.reset(2); 2105 } 2106 } else if (yOff > 0) { 2107 movement = y.generate((2/event.getYPrecision())); 2108 if (movement != 0) { 2109 keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN 2110 : KeyEvent.KEYCODE_DPAD_UP; 2111 accel = y.acceleration; 2112 x.reset(2); 2113 } 2114 } 2115 2116 if (keycode != 0) { 2117 if (movement < 0) movement = -movement; 2118 int accelMovement = (int)(movement * accel); 2119 if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement 2120 + " accelMovement=" + accelMovement 2121 + " accel=" + accel); 2122 if (accelMovement > movement) { 2123 if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " 2124 + keycode); 2125 movement--; 2126 deliverKeyEvent(new KeyEvent(curTime, curTime, 2127 KeyEvent.ACTION_MULTIPLE, keycode, 2128 accelMovement-movement, metastate), false); 2129 } 2130 while (movement > 0) { 2131 if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " 2132 + keycode); 2133 movement--; 2134 curTime = SystemClock.uptimeMillis(); 2135 deliverKeyEvent(new KeyEvent(curTime, curTime, 2136 KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false); 2137 deliverKeyEvent(new KeyEvent(curTime, curTime, 2138 KeyEvent.ACTION_UP, keycode, 0, metastate), false); 2139 } 2140 mLastTrackballTime = curTime; 2141 } 2142 } finally { 2143 if (callWhenDone) { 2144 try { 2145 sWindowSession.finishKey(mWindow); 2146 } catch (RemoteException e) { 2147 } 2148 if (event != null) { 2149 event.recycle(); 2150 } 2151 } 2152 // Let the exception fall through -- the looper will catch 2153 // it and take care of the bad app for us. 2154 } 2155 } 2156 2157 /** 2158 * @param keyCode The key code 2159 * @return True if the key is directional. 2160 */ 2161 static boolean isDirectional(int keyCode) { 2162 switch (keyCode) { 2163 case KeyEvent.KEYCODE_DPAD_LEFT: 2164 case KeyEvent.KEYCODE_DPAD_RIGHT: 2165 case KeyEvent.KEYCODE_DPAD_UP: 2166 case KeyEvent.KEYCODE_DPAD_DOWN: 2167 return true; 2168 } 2169 return false; 2170 } 2171 2172 /** 2173 * Returns true if this key is a keyboard key. 2174 * @param keyEvent The key event. 2175 * @return whether this key is a keyboard key. 2176 */ 2177 private static boolean isKeyboardKey(KeyEvent keyEvent) { 2178 final int convertedKey = keyEvent.getUnicodeChar(); 2179 return convertedKey > 0; 2180 } 2181 2182 2183 2184 /** 2185 * See if the key event means we should leave touch mode (and leave touch 2186 * mode if so). 2187 * @param event The key event. 2188 * @return Whether this key event should be consumed (meaning the act of 2189 * leaving touch mode alone is considered the event). 2190 */ 2191 private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) { 2192 if (event.getAction() != KeyEvent.ACTION_DOWN) { 2193 return false; 2194 } 2195 if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) { 2196 return false; 2197 } 2198 2199 // only relevant if we are in touch mode 2200 if (!mAttachInfo.mInTouchMode) { 2201 return false; 2202 } 2203 2204 // if something like an edit text has focus and the user is typing, 2205 // leave touch mode 2206 // 2207 // note: the condition of not being a keyboard key is kind of a hacky 2208 // approximation of whether we think the focused view will want the 2209 // key; if we knew for sure whether the focused view would consume 2210 // the event, that would be better. 2211 if (isKeyboardKey(event) && mView != null && mView.hasFocus()) { 2212 mFocusedView = mView.findFocus(); 2213 if ((mFocusedView instanceof ViewGroup) 2214 && ((ViewGroup) mFocusedView).getDescendantFocusability() == 2215 ViewGroup.FOCUS_AFTER_DESCENDANTS) { 2216 // something has focus, but is holding it weakly as a container 2217 return false; 2218 } 2219 if (ensureTouchMode(false)) { 2220 throw new IllegalStateException("should not have changed focus " 2221 + "when leaving touch mode while a view has focus."); 2222 } 2223 return false; 2224 } 2225 2226 if (isDirectional(event.getKeyCode())) { 2227 // no view has focus, so we leave touch mode (and find something 2228 // to give focus to). the event is consumed if we were able to 2229 // find something to give focus to. 2230 return ensureTouchMode(false); 2231 } 2232 return false; 2233 } 2234 2235 /** 2236 * log motion events 2237 */ 2238 private static void captureMotionLog(String subTag, MotionEvent ev) { 2239 //check dynamic switch 2240 if (ev == null || 2241 SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) { 2242 return; 2243 } 2244 2245 StringBuilder sb = new StringBuilder(subTag + ": "); 2246 sb.append(ev.getDownTime()).append(','); 2247 sb.append(ev.getEventTime()).append(','); 2248 sb.append(ev.getAction()).append(','); 2249 sb.append(ev.getX()).append(','); 2250 sb.append(ev.getY()).append(','); 2251 sb.append(ev.getPressure()).append(','); 2252 sb.append(ev.getSize()).append(','); 2253 sb.append(ev.getMetaState()).append(','); 2254 sb.append(ev.getXPrecision()).append(','); 2255 sb.append(ev.getYPrecision()).append(','); 2256 sb.append(ev.getDeviceId()).append(','); 2257 sb.append(ev.getEdgeFlags()); 2258 Log.d(TAG, sb.toString()); 2259 } 2260 /** 2261 * log motion events 2262 */ 2263 private static void captureKeyLog(String subTag, KeyEvent ev) { 2264 //check dynamic switch 2265 if (ev == null || 2266 SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) { 2267 return; 2268 } 2269 StringBuilder sb = new StringBuilder(subTag + ": "); 2270 sb.append(ev.getDownTime()).append(','); 2271 sb.append(ev.getEventTime()).append(','); 2272 sb.append(ev.getAction()).append(','); 2273 sb.append(ev.getKeyCode()).append(','); 2274 sb.append(ev.getRepeatCount()).append(','); 2275 sb.append(ev.getMetaState()).append(','); 2276 sb.append(ev.getDeviceId()).append(','); 2277 sb.append(ev.getScanCode()); 2278 Log.d(TAG, sb.toString()); 2279 } 2280 2281 int enqueuePendingEvent(Object event, boolean sendDone) { 2282 int seq = mPendingEventSeq+1; 2283 if (seq < 0) seq = 0; 2284 mPendingEventSeq = seq; 2285 mPendingEvents.put(seq, event); 2286 return sendDone ? seq : -seq; 2287 } 2288 2289 Object retrievePendingEvent(int seq) { 2290 if (seq < 0) seq = -seq; 2291 Object event = mPendingEvents.get(seq); 2292 if (event != null) { 2293 mPendingEvents.remove(seq); 2294 } 2295 return event; 2296 } 2297 2298 private void deliverKeyEvent(KeyEvent event, boolean sendDone) { 2299 // If mView is null, we just consume the key event because it doesn't 2300 // make sense to do anything else with it. 2301 boolean handled = mView != null 2302 ? mView.dispatchKeyEventPreIme(event) : true; 2303 if (handled) { 2304 if (sendDone) { 2305 if (LOCAL_LOGV) Log.v( 2306 "ViewRoot", "Telling window manager key is finished"); 2307 try { 2308 sWindowSession.finishKey(mWindow); 2309 } catch (RemoteException e) { 2310 } 2311 } 2312 return; 2313 } 2314 // If it is possible for this window to interact with the input 2315 // method window, then we want to first dispatch our key events 2316 // to the input method. 2317 if (mLastWasImTarget) { 2318 InputMethodManager imm = InputMethodManager.peekInstance(); 2319 if (imm != null && mView != null) { 2320 int seq = enqueuePendingEvent(event, sendDone); 2321 if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" 2322 + seq + " event=" + event); 2323 imm.dispatchKeyEvent(mView.getContext(), seq, event, 2324 mInputMethodCallback); 2325 return; 2326 } 2327 } 2328 deliverKeyEventToViewHierarchy(event, sendDone); 2329 } 2330 2331 void handleFinishedEvent(int seq, boolean handled) { 2332 final KeyEvent event = (KeyEvent)retrievePendingEvent(seq); 2333 if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq 2334 + " handled=" + handled + " event=" + event); 2335 if (event != null) { 2336 final boolean sendDone = seq >= 0; 2337 if (!handled) { 2338 deliverKeyEventToViewHierarchy(event, sendDone); 2339 return; 2340 } else if (sendDone) { 2341 if (LOCAL_LOGV) Log.v( 2342 "ViewRoot", "Telling window manager key is finished"); 2343 try { 2344 sWindowSession.finishKey(mWindow); 2345 } catch (RemoteException e) { 2346 } 2347 } else { 2348 Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq 2349 + " handled=" + handled + " ev=" + event 2350 + ") neither delivering nor finishing key"); 2351 } 2352 } 2353 } 2354 2355 private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) { 2356 try { 2357 if (mView != null && mAdded) { 2358 final int action = event.getAction(); 2359 boolean isDown = (action == KeyEvent.ACTION_DOWN); 2360 2361 if (checkForLeavingTouchModeAndConsume(event)) { 2362 return; 2363 } 2364 2365 if (Config.LOGV) { 2366 captureKeyLog("captureDispatchKeyEvent", event); 2367 } 2368 boolean keyHandled = mView.dispatchKeyEvent(event); 2369 2370 if (!keyHandled && isDown) { 2371 int direction = 0; 2372 switch (event.getKeyCode()) { 2373 case KeyEvent.KEYCODE_DPAD_LEFT: 2374 direction = View.FOCUS_LEFT; 2375 break; 2376 case KeyEvent.KEYCODE_DPAD_RIGHT: 2377 direction = View.FOCUS_RIGHT; 2378 break; 2379 case KeyEvent.KEYCODE_DPAD_UP: 2380 direction = View.FOCUS_UP; 2381 break; 2382 case KeyEvent.KEYCODE_DPAD_DOWN: 2383 direction = View.FOCUS_DOWN; 2384 break; 2385 } 2386 2387 if (direction != 0) { 2388 2389 View focused = mView != null ? mView.findFocus() : null; 2390 if (focused != null) { 2391 View v = focused.focusSearch(direction); 2392 boolean focusPassed = false; 2393 if (v != null && v != focused) { 2394 // do the math the get the interesting rect 2395 // of previous focused into the coord system of 2396 // newly focused view 2397 focused.getFocusedRect(mTempRect); 2398 ((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect); 2399 ((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect); 2400 focusPassed = v.requestFocus(direction, mTempRect); 2401 } 2402 2403 if (!focusPassed) { 2404 mView.dispatchUnhandledMove(focused, direction); 2405 } else { 2406 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 2407 } 2408 } 2409 } 2410 } 2411 } 2412 2413 } finally { 2414 if (sendDone) { 2415 if (LOCAL_LOGV) Log.v( 2416 "ViewRoot", "Telling window manager key is finished"); 2417 try { 2418 sWindowSession.finishKey(mWindow); 2419 } catch (RemoteException e) { 2420 } 2421 } 2422 // Let the exception fall through -- the looper will catch 2423 // it and take care of the bad app for us. 2424 } 2425 } 2426 2427 private AudioManager getAudioManager() { 2428 if (mView == null) { 2429 throw new IllegalStateException("getAudioManager called when there is no mView"); 2430 } 2431 if (mAudioManager == null) { 2432 mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE); 2433 } 2434 return mAudioManager; 2435 } 2436 2437 private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, 2438 boolean insetsPending) throws RemoteException { 2439 2440 float appScale = mAttachInfo.mApplicationScale; 2441 boolean restore = false; 2442 if (params != null && mTranslator != null) { 2443 restore = true; 2444 params.backup(); 2445 mTranslator.translateWindowLayout(params); 2446 } 2447 if (params != null) { 2448 if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params); 2449 } 2450 int relayoutResult = sWindowSession.relayout( 2451 mWindow, params, 2452 (int) (mView.mMeasuredWidth * appScale + 0.5f), 2453 (int) (mView.mMeasuredHeight * appScale + 0.5f), 2454 viewVisibility, insetsPending, mWinFrame, 2455 mPendingContentInsets, mPendingVisibleInsets, mSurface); 2456 if (restore) { 2457 params.restore(); 2458 } 2459 2460 if (mTranslator != null) { 2461 mTranslator.translateRectInScreenToAppWinFrame(mWinFrame); 2462 mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets); 2463 mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets); 2464 } 2465 return relayoutResult; 2466 } 2467 2468 /** 2469 * {@inheritDoc} 2470 */ 2471 public void playSoundEffect(int effectId) { 2472 checkThread(); 2473 2474 final AudioManager audioManager = getAudioManager(); 2475 2476 switch (effectId) { 2477 case SoundEffectConstants.CLICK: 2478 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 2479 return; 2480 case SoundEffectConstants.NAVIGATION_DOWN: 2481 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); 2482 return; 2483 case SoundEffectConstants.NAVIGATION_LEFT: 2484 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); 2485 return; 2486 case SoundEffectConstants.NAVIGATION_RIGHT: 2487 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); 2488 return; 2489 case SoundEffectConstants.NAVIGATION_UP: 2490 audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP); 2491 return; 2492 default: 2493 throw new IllegalArgumentException("unknown effect id " + effectId + 2494 " not defined in " + SoundEffectConstants.class.getCanonicalName()); 2495 } 2496 } 2497 2498 /** 2499 * {@inheritDoc} 2500 */ 2501 public boolean performHapticFeedback(int effectId, boolean always) { 2502 try { 2503 return sWindowSession.performHapticFeedback(mWindow, effectId, always); 2504 } catch (RemoteException e) { 2505 return false; 2506 } 2507 } 2508 2509 /** 2510 * {@inheritDoc} 2511 */ 2512 public View focusSearch(View focused, int direction) { 2513 checkThread(); 2514 if (!(mView instanceof ViewGroup)) { 2515 return null; 2516 } 2517 return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction); 2518 } 2519 2520 public void debug() { 2521 mView.debug(); 2522 } 2523 2524 public void die(boolean immediate) { 2525 if (immediate) { 2526 doDie(); 2527 } else { 2528 sendEmptyMessage(DIE); 2529 } 2530 } 2531 2532 void doDie() { 2533 checkThread(); 2534 if (Config.LOGV) Log.v("ViewRoot", "DIE in " + this + " of " + mSurface); 2535 synchronized (this) { 2536 if (mAdded && !mFirst) { 2537 int viewVisibility = mView.getVisibility(); 2538 boolean viewVisibilityChanged = mViewVisibility != viewVisibility; 2539 if (mWindowAttributesChanged || viewVisibilityChanged) { 2540 // If layout params have been changed, first give them 2541 // to the window manager to make sure it has the correct 2542 // animation info. 2543 try { 2544 if ((relayoutWindow(mWindowAttributes, viewVisibility, false) 2545 & WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { 2546 sWindowSession.finishDrawing(mWindow); 2547 } 2548 } catch (RemoteException e) { 2549 } 2550 } 2551 2552 mSurface.release(); 2553 } 2554 if (mAdded) { 2555 mAdded = false; 2556 dispatchDetachedFromWindow(); 2557 } 2558 } 2559 } 2560 2561 public void dispatchFinishedEvent(int seq, boolean handled) { 2562 Message msg = obtainMessage(FINISHED_EVENT); 2563 msg.arg1 = seq; 2564 msg.arg2 = handled ? 1 : 0; 2565 sendMessage(msg); 2566 } 2567 2568 public void dispatchResized(int w, int h, Rect coveredInsets, 2569 Rect visibleInsets, boolean reportDraw) { 2570 if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w 2571 + " h=" + h + " coveredInsets=" + coveredInsets.toShortString() 2572 + " visibleInsets=" + visibleInsets.toShortString() 2573 + " reportDraw=" + reportDraw); 2574 Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED); 2575 if (mTranslator != null) { 2576 mTranslator.translateRectInScreenToAppWindow(coveredInsets); 2577 mTranslator.translateRectInScreenToAppWindow(visibleInsets); 2578 w *= mTranslator.applicationInvertedScale; 2579 h *= mTranslator.applicationInvertedScale; 2580 } 2581 msg.arg1 = w; 2582 msg.arg2 = h; 2583 msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) }; 2584 sendMessage(msg); 2585 } 2586 2587 public void dispatchKey(KeyEvent event) { 2588 if (event.getAction() == KeyEvent.ACTION_DOWN) { 2589 //noinspection ConstantConditions 2590 if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { 2591 if (Config.LOGD) Log.d("keydisp", 2592 "==================================================="); 2593 if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:"); 2594 debug(); 2595 2596 if (Config.LOGD) Log.d("keydisp", 2597 "==================================================="); 2598 } 2599 } 2600 2601 Message msg = obtainMessage(DISPATCH_KEY); 2602 msg.obj = event; 2603 2604 if (LOCAL_LOGV) Log.v( 2605 "ViewRoot", "sending key " + event + " to " + mView); 2606 2607 sendMessageAtTime(msg, event.getEventTime()); 2608 } 2609 2610 public void dispatchPointer(MotionEvent event, long eventTime, 2611 boolean callWhenDone) { 2612 Message msg = obtainMessage(DISPATCH_POINTER); 2613 msg.obj = event; 2614 msg.arg1 = callWhenDone ? 1 : 0; 2615 sendMessageAtTime(msg, eventTime); 2616 } 2617 2618 public void dispatchTrackball(MotionEvent event, long eventTime, 2619 boolean callWhenDone) { 2620 Message msg = obtainMessage(DISPATCH_TRACKBALL); 2621 msg.obj = event; 2622 msg.arg1 = callWhenDone ? 1 : 0; 2623 sendMessageAtTime(msg, eventTime); 2624 } 2625 2626 public void dispatchAppVisibility(boolean visible) { 2627 Message msg = obtainMessage(DISPATCH_APP_VISIBILITY); 2628 msg.arg1 = visible ? 1 : 0; 2629 sendMessage(msg); 2630 } 2631 2632 public void dispatchGetNewSurface() { 2633 Message msg = obtainMessage(DISPATCH_GET_NEW_SURFACE); 2634 sendMessage(msg); 2635 } 2636 2637 public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { 2638 Message msg = Message.obtain(); 2639 msg.what = WINDOW_FOCUS_CHANGED; 2640 msg.arg1 = hasFocus ? 1 : 0; 2641 msg.arg2 = inTouchMode ? 1 : 0; 2642 sendMessage(msg); 2643 } 2644 2645 public void dispatchCloseSystemDialogs(String reason) { 2646 Message msg = Message.obtain(); 2647 msg.what = CLOSE_SYSTEM_DIALOGS; 2648 msg.obj = reason; 2649 sendMessage(msg); 2650 } 2651 2652 /** 2653 * The window is getting focus so if there is anything focused/selected 2654 * send an {@link AccessibilityEvent} to announce that. 2655 */ 2656 private void sendAccessibilityEvents() { 2657 if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) { 2658 return; 2659 } 2660 mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 2661 View focusedView = mView.findFocus(); 2662 if (focusedView != null && focusedView != mView) { 2663 focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); 2664 } 2665 } 2666 2667 public boolean showContextMenuForChild(View originalView) { 2668 return false; 2669 } 2670 2671 public void createContextMenu(ContextMenu menu) { 2672 } 2673 2674 public void childDrawableStateChanged(View child) { 2675 } 2676 2677 protected Rect getWindowFrame() { 2678 return mWinFrame; 2679 } 2680 2681 void checkThread() { 2682 if (mThread != Thread.currentThread()) { 2683 throw new CalledFromWrongThreadException( 2684 "Only the original thread that created a view hierarchy can touch its views."); 2685 } 2686 } 2687 2688 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 2689 // ViewRoot never intercepts touch event, so this can be a no-op 2690 } 2691 2692 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, 2693 boolean immediate) { 2694 return scrollToRectOrFocus(rectangle, immediate); 2695 } 2696 2697 static class InputMethodCallback extends IInputMethodCallback.Stub { 2698 private WeakReference<ViewRoot> mViewRoot; 2699 2700 public InputMethodCallback(ViewRoot viewRoot) { 2701 mViewRoot = new WeakReference<ViewRoot>(viewRoot); 2702 } 2703 2704 public void finishedEvent(int seq, boolean handled) { 2705 final ViewRoot viewRoot = mViewRoot.get(); 2706 if (viewRoot != null) { 2707 viewRoot.dispatchFinishedEvent(seq, handled); 2708 } 2709 } 2710 2711 public void sessionCreated(IInputMethodSession session) throws RemoteException { 2712 // Stub -- not for use in the client. 2713 } 2714 } 2715 2716 static class EventCompletion extends Handler { 2717 final IWindow mWindow; 2718 final KeyEvent mKeyEvent; 2719 final boolean mIsPointer; 2720 final MotionEvent mMotionEvent; 2721 2722 EventCompletion(Looper looper, IWindow window, KeyEvent key, 2723 boolean isPointer, MotionEvent motion) { 2724 super(looper); 2725 mWindow = window; 2726 mKeyEvent = key; 2727 mIsPointer = isPointer; 2728 mMotionEvent = motion; 2729 sendEmptyMessage(0); 2730 } 2731 2732 @Override 2733 public void handleMessage(Message msg) { 2734 if (mKeyEvent != null) { 2735 try { 2736 sWindowSession.finishKey(mWindow); 2737 } catch (RemoteException e) { 2738 } 2739 } else if (mIsPointer) { 2740 boolean didFinish; 2741 MotionEvent event = mMotionEvent; 2742 if (event == null) { 2743 try { 2744 event = sWindowSession.getPendingPointerMove(mWindow); 2745 } catch (RemoteException e) { 2746 } 2747 didFinish = true; 2748 } else { 2749 didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; 2750 } 2751 if (!didFinish) { 2752 try { 2753 sWindowSession.finishKey(mWindow); 2754 } catch (RemoteException e) { 2755 } 2756 } 2757 } else { 2758 MotionEvent event = mMotionEvent; 2759 if (event == null) { 2760 try { 2761 event = sWindowSession.getPendingTrackballMove(mWindow); 2762 } catch (RemoteException e) { 2763 } 2764 } else { 2765 try { 2766 sWindowSession.finishKey(mWindow); 2767 } catch (RemoteException e) { 2768 } 2769 } 2770 } 2771 } 2772 } 2773 2774 static class W extends IWindow.Stub { 2775 private final WeakReference<ViewRoot> mViewRoot; 2776 private final Looper mMainLooper; 2777 2778 public W(ViewRoot viewRoot, Context context) { 2779 mViewRoot = new WeakReference<ViewRoot>(viewRoot); 2780 mMainLooper = context.getMainLooper(); 2781 } 2782 2783 public void resized(int w, int h, Rect coveredInsets, 2784 Rect visibleInsets, boolean reportDraw) { 2785 final ViewRoot viewRoot = mViewRoot.get(); 2786 if (viewRoot != null) { 2787 viewRoot.dispatchResized(w, h, coveredInsets, 2788 visibleInsets, reportDraw); 2789 } 2790 } 2791 2792 public void dispatchKey(KeyEvent event) { 2793 final ViewRoot viewRoot = mViewRoot.get(); 2794 if (viewRoot != null) { 2795 viewRoot.dispatchKey(event); 2796 } else { 2797 Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); 2798 new EventCompletion(mMainLooper, this, event, false, null); 2799 } 2800 } 2801 2802 public void dispatchPointer(MotionEvent event, long eventTime, 2803 boolean callWhenDone) { 2804 final ViewRoot viewRoot = mViewRoot.get(); 2805 if (viewRoot != null) { 2806 if (MEASURE_LATENCY) { 2807 // Note: eventTime is in milliseconds 2808 ViewRoot.lt.sample("* ViewRoot b4 dispatchPtr", System.nanoTime() - eventTime * 1000000); 2809 } 2810 viewRoot.dispatchPointer(event, eventTime, callWhenDone); 2811 } else { 2812 new EventCompletion(mMainLooper, this, null, true, event); 2813 } 2814 } 2815 2816 public void dispatchTrackball(MotionEvent event, long eventTime, 2817 boolean callWhenDone) { 2818 final ViewRoot viewRoot = mViewRoot.get(); 2819 if (viewRoot != null) { 2820 viewRoot.dispatchTrackball(event, eventTime, callWhenDone); 2821 } else { 2822 new EventCompletion(mMainLooper, this, null, false, event); 2823 } 2824 } 2825 2826 public void dispatchAppVisibility(boolean visible) { 2827 final ViewRoot viewRoot = mViewRoot.get(); 2828 if (viewRoot != null) { 2829 viewRoot.dispatchAppVisibility(visible); 2830 } 2831 } 2832 2833 public void dispatchGetNewSurface() { 2834 final ViewRoot viewRoot = mViewRoot.get(); 2835 if (viewRoot != null) { 2836 viewRoot.dispatchGetNewSurface(); 2837 } 2838 } 2839 2840 public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { 2841 final ViewRoot viewRoot = mViewRoot.get(); 2842 if (viewRoot != null) { 2843 viewRoot.windowFocusChanged(hasFocus, inTouchMode); 2844 } 2845 } 2846 2847 private static int checkCallingPermission(String permission) { 2848 if (!Process.supportsProcesses()) { 2849 return PackageManager.PERMISSION_GRANTED; 2850 } 2851 2852 try { 2853 return ActivityManagerNative.getDefault().checkPermission( 2854 permission, Binder.getCallingPid(), Binder.getCallingUid()); 2855 } catch (RemoteException e) { 2856 return PackageManager.PERMISSION_DENIED; 2857 } 2858 } 2859 2860 public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { 2861 final ViewRoot viewRoot = mViewRoot.get(); 2862 if (viewRoot != null) { 2863 final View view = viewRoot.mView; 2864 if (view != null) { 2865 if (checkCallingPermission(Manifest.permission.DUMP) != 2866 PackageManager.PERMISSION_GRANTED) { 2867 throw new SecurityException("Insufficient permissions to invoke" 2868 + " executeCommand() from pid=" + Binder.getCallingPid() 2869 + ", uid=" + Binder.getCallingUid()); 2870 } 2871 2872 OutputStream clientStream = null; 2873 try { 2874 clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out); 2875 ViewDebug.dispatchCommand(view, command, parameters, clientStream); 2876 } catch (IOException e) { 2877 e.printStackTrace(); 2878 } finally { 2879 if (clientStream != null) { 2880 try { 2881 clientStream.close(); 2882 } catch (IOException e) { 2883 e.printStackTrace(); 2884 } 2885 } 2886 } 2887 } 2888 } 2889 } 2890 2891 public void closeSystemDialogs(String reason) { 2892 final ViewRoot viewRoot = mViewRoot.get(); 2893 if (viewRoot != null) { 2894 viewRoot.dispatchCloseSystemDialogs(reason); 2895 } 2896 } 2897 2898 public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, 2899 boolean sync) { 2900 if (sync) { 2901 try { 2902 sWindowSession.wallpaperOffsetsComplete(asBinder()); 2903 } catch (RemoteException e) { 2904 } 2905 } 2906 } 2907 2908 public void dispatchWallpaperCommand(String action, int x, int y, 2909 int z, Bundle extras, boolean sync) { 2910 if (sync) { 2911 try { 2912 sWindowSession.wallpaperCommandComplete(asBinder(), null); 2913 } catch (RemoteException e) { 2914 } 2915 } 2916 } 2917 } 2918 2919 /** 2920 * Maintains state information for a single trackball axis, generating 2921 * discrete (DPAD) movements based on raw trackball motion. 2922 */ 2923 static final class TrackballAxis { 2924 /** 2925 * The maximum amount of acceleration we will apply. 2926 */ 2927 static final float MAX_ACCELERATION = 20; 2928 2929 /** 2930 * The maximum amount of time (in milliseconds) between events in order 2931 * for us to consider the user to be doing fast trackball movements, 2932 * and thus apply an acceleration. 2933 */ 2934 static final long FAST_MOVE_TIME = 150; 2935 2936 /** 2937 * Scaling factor to the time (in milliseconds) between events to how 2938 * much to multiple/divide the current acceleration. When movement 2939 * is < FAST_MOVE_TIME this multiplies the acceleration; when > 2940 * FAST_MOVE_TIME it divides it. 2941 */ 2942 static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40); 2943 2944 float position; 2945 float absPosition; 2946 float acceleration = 1; 2947 long lastMoveTime = 0; 2948 int step; 2949 int dir; 2950 int nonAccelMovement; 2951 2952 void reset(int _step) { 2953 position = 0; 2954 acceleration = 1; 2955 lastMoveTime = 0; 2956 step = _step; 2957 dir = 0; 2958 } 2959 2960 /** 2961 * Add trackball movement into the state. If the direction of movement 2962 * has been reversed, the state is reset before adding the 2963 * movement (so that you don't have to compensate for any previously 2964 * collected movement before see the result of the movement in the 2965 * new direction). 2966 * 2967 * @return Returns the absolute value of the amount of movement 2968 * collected so far. 2969 */ 2970 float collect(float off, long time, String axis) { 2971 long normTime; 2972 if (off > 0) { 2973 normTime = (long)(off * FAST_MOVE_TIME); 2974 if (dir < 0) { 2975 if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!"); 2976 position = 0; 2977 step = 0; 2978 acceleration = 1; 2979 lastMoveTime = 0; 2980 } 2981 dir = 1; 2982 } else if (off < 0) { 2983 normTime = (long)((-off) * FAST_MOVE_TIME); 2984 if (dir > 0) { 2985 if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!"); 2986 position = 0; 2987 step = 0; 2988 acceleration = 1; 2989 lastMoveTime = 0; 2990 } 2991 dir = -1; 2992 } else { 2993 normTime = 0; 2994 } 2995 2996 // The number of milliseconds between each movement that is 2997 // considered "normal" and will not result in any acceleration 2998 // or deceleration, scaled by the offset we have here. 2999 if (normTime > 0) { 3000 long delta = time - lastMoveTime; 3001 lastMoveTime = time; 3002 float acc = acceleration; 3003 if (delta < normTime) { 3004 // The user is scrolling rapidly, so increase acceleration. 3005 float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR; 3006 if (scale > 1) acc *= scale; 3007 if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off=" 3008 + off + " normTime=" + normTime + " delta=" + delta 3009 + " scale=" + scale + " acc=" + acc); 3010 acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION; 3011 } else { 3012 // The user is scrolling slowly, so decrease acceleration. 3013 float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR; 3014 if (scale > 1) acc /= scale; 3015 if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off=" 3016 + off + " normTime=" + normTime + " delta=" + delta 3017 + " scale=" + scale + " acc=" + acc); 3018 acceleration = acc > 1 ? acc : 1; 3019 } 3020 } 3021 position += off; 3022 return (absPosition = Math.abs(position)); 3023 } 3024 3025 /** 3026 * Generate the number of discrete movement events appropriate for 3027 * the currently collected trackball movement. 3028 * 3029 * @param precision The minimum movement required to generate the 3030 * first discrete movement. 3031 * 3032 * @return Returns the number of discrete movements, either positive 3033 * or negative, or 0 if there is not enough trackball movement yet 3034 * for a discrete movement. 3035 */ 3036 int generate(float precision) { 3037 int movement = 0; 3038 nonAccelMovement = 0; 3039 do { 3040 final int dir = position >= 0 ? 1 : -1; 3041 switch (step) { 3042 // If we are going to execute the first step, then we want 3043 // to do this as soon as possible instead of waiting for 3044 // a full movement, in order to make things look responsive. 3045 case 0: 3046 if (absPosition < precision) { 3047 return movement; 3048 } 3049 movement += dir; 3050 nonAccelMovement += dir; 3051 step = 1; 3052 break; 3053 // If we have generated the first movement, then we need 3054 // to wait for the second complete trackball motion before 3055 // generating the second discrete movement. 3056 case 1: 3057 if (absPosition < 2) { 3058 return movement; 3059 } 3060 movement += dir; 3061 nonAccelMovement += dir; 3062 position += dir > 0 ? -2 : 2; 3063 absPosition = Math.abs(position); 3064 step = 2; 3065 break; 3066 // After the first two, we generate discrete movements 3067 // consistently with the trackball, applying an acceleration 3068 // if the trackball is moving quickly. This is a simple 3069 // acceleration on top of what we already compute based 3070 // on how quickly the wheel is being turned, to apply 3071 // a longer increasing acceleration to continuous movement 3072 // in one direction. 3073 default: 3074 if (absPosition < 1) { 3075 return movement; 3076 } 3077 movement += dir; 3078 position += dir >= 0 ? -1 : 1; 3079 absPosition = Math.abs(position); 3080 float acc = acceleration; 3081 acc *= 1.1f; 3082 acceleration = acc < MAX_ACCELERATION ? acc : acceleration; 3083 break; 3084 } 3085 } while (true); 3086 } 3087 } 3088 3089 public static final class CalledFromWrongThreadException extends AndroidRuntimeException { 3090 public CalledFromWrongThreadException(String msg) { 3091 super(msg); 3092 } 3093 } 3094 3095 private SurfaceHolder mHolder = new SurfaceHolder() { 3096 // we only need a SurfaceHolder for opengl. it would be nice 3097 // to implement everything else though, especially the callback 3098 // support (opengl doesn't make use of it right now, but eventually 3099 // will). 3100 public Surface getSurface() { 3101 return mSurface; 3102 } 3103 3104 public boolean isCreating() { 3105 return false; 3106 } 3107 3108 public void addCallback(Callback callback) { 3109 } 3110 3111 public void removeCallback(Callback callback) { 3112 } 3113 3114 public void setFixedSize(int width, int height) { 3115 } 3116 3117 public void setSizeFromLayout() { 3118 } 3119 3120 public void setFormat(int format) { 3121 } 3122 3123 public void setType(int type) { 3124 } 3125 3126 public void setKeepScreenOn(boolean screenOn) { 3127 } 3128 3129 public Canvas lockCanvas() { 3130 return null; 3131 } 3132 3133 public Canvas lockCanvas(Rect dirty) { 3134 return null; 3135 } 3136 3137 public void unlockCanvasAndPost(Canvas canvas) { 3138 } 3139 public Rect getSurfaceFrame() { 3140 return null; 3141 } 3142 }; 3143 3144 static RunQueue getRunQueue() { 3145 RunQueue rq = sRunQueues.get(); 3146 if (rq != null) { 3147 return rq; 3148 } 3149 rq = new RunQueue(); 3150 sRunQueues.set(rq); 3151 return rq; 3152 } 3153 3154 /** 3155 * @hide 3156 */ 3157 static final class RunQueue { 3158 private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); 3159 3160 void post(Runnable action) { 3161 postDelayed(action, 0); 3162 } 3163 3164 void postDelayed(Runnable action, long delayMillis) { 3165 HandlerAction handlerAction = new HandlerAction(); 3166 handlerAction.action = action; 3167 handlerAction.delay = delayMillis; 3168 3169 synchronized (mActions) { 3170 mActions.add(handlerAction); 3171 } 3172 } 3173 3174 void removeCallbacks(Runnable action) { 3175 final HandlerAction handlerAction = new HandlerAction(); 3176 handlerAction.action = action; 3177 3178 synchronized (mActions) { 3179 final ArrayList<HandlerAction> actions = mActions; 3180 3181 while (actions.remove(handlerAction)) { 3182 // Keep going 3183 } 3184 } 3185 } 3186 3187 void executeActions(Handler handler) { 3188 synchronized (mActions) { 3189 final ArrayList<HandlerAction> actions = mActions; 3190 final int count = actions.size(); 3191 3192 for (int i = 0; i < count; i++) { 3193 final HandlerAction handlerAction = actions.get(i); 3194 handler.postDelayed(handlerAction.action, handlerAction.delay); 3195 } 3196 3197 actions.clear(); 3198 } 3199 } 3200 3201 private static class HandlerAction { 3202 Runnable action; 3203 long delay; 3204 3205 @Override 3206 public boolean equals(Object o) { 3207 if (this == o) return true; 3208 if (o == null || getClass() != o.getClass()) return false; 3209 3210 HandlerAction that = (HandlerAction) o; 3211 return !(action != null ? !action.equals(that.action) : that.action != null); 3212 3213 } 3214 3215 @Override 3216 public int hashCode() { 3217 int result = action != null ? action.hashCode() : 0; 3218 result = 31 * result + (int) (delay ^ (delay >>> 32)); 3219 return result; 3220 } 3221 } 3222 } 3223 3224 private static native void nativeShowFPS(Canvas canvas, int durationMillis); 3225 3226 // inform skia to just abandon its texture cache IDs 3227 // doesn't call glDeleteTextures 3228 private static native void nativeAbandonGlCaches(); 3229} 3230