/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.opengl.ManagedEGLContext; import android.os.IBinder; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.inputmethod.InputMethodManager; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.HashMap; final class WindowLeaked extends AndroidRuntimeException { public WindowLeaked(String msg) { super(msg); } } /** * Low-level communication with the global system window manager. It implements * the ViewManager interface, allowing you to add any View subclass as a * top-level window on the screen. Additional window manager specific layout * parameters are defined for control over how windows are displayed. * It also implemens the WindowManager interface, allowing you to control the * displays attached to the device. * * <p>Applications will not normally use WindowManager directly, instead relying * on the higher-level facilities in {@link android.app.Activity} and * {@link android.app.Dialog}. * * <p>Even for low-level window manager access, it is almost never correct to use * this class. For example, {@link android.app.Activity#getWindowManager} * provides a ViewManager for adding windows that are associated with that * activity -- the window manager will not normally allow you to add arbitrary * windows that are not associated with an activity. * * @hide */ public class WindowManagerImpl implements WindowManager { /** * The user is navigating with keys (not the touch screen), so * navigational focus should be shown. */ public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1; /** * This is the first time the window is being drawn, * so the client must call drawingFinished() when done */ public static final int RELAYOUT_RES_FIRST_TIME = 0x2; /** * The window manager has changed the surface from the last call. */ public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4; /** * Flag for relayout: the client will be later giving * internal insets; as a result, the window will not impact other window * layouts until the insets are given. */ public static final int RELAYOUT_INSETS_PENDING = 0x1; /** * Flag for relayout: the client may be currently using the current surface, * so if it is to be destroyed as a part of the relayout the destroy must * be deferred until later. The client will call performDeferredDestroy() * when it is okay. */ public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2; public static final int ADD_FLAG_APP_VISIBLE = 0x2; public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE; public static final int ADD_OKAY = 0; public static final int ADD_BAD_APP_TOKEN = -1; public static final int ADD_BAD_SUBWINDOW_TOKEN = -2; public static final int ADD_NOT_APP_TOKEN = -3; public static final int ADD_APP_EXITING = -4; public static final int ADD_DUPLICATE_ADD = -5; public static final int ADD_STARTING_NOT_NEEDED = -6; public static final int ADD_MULTIPLE_SINGLETON = -7; public static final int ADD_PERMISSION_DENIED = -8; private View[] mViews; private ViewRootImpl[] mRoots; private WindowManager.LayoutParams[] mParams; private final static Object sLock = new Object(); private final static WindowManagerImpl sWindowManager = new WindowManagerImpl(); private final static HashMap<CompatibilityInfo, WindowManager> sCompatWindowManagers = new HashMap<CompatibilityInfo, WindowManager>(); static class CompatModeWrapper implements WindowManager { private final WindowManagerImpl mWindowManager; private final Display mDefaultDisplay; private final CompatibilityInfoHolder mCompatibilityInfo; CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) { mWindowManager = wm instanceof CompatModeWrapper ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm; // Use the original display if there is no compatibility mode // to apply, or the underlying window manager is already a // compatibility mode wrapper. (We assume that if it is a // wrapper, it is applying the same compatibility mode.) if (ci == null) { mDefaultDisplay = mWindowManager.getDefaultDisplay(); } else { //mDefaultDisplay = mWindowManager.getDefaultDisplay(); mDefaultDisplay = Display.createCompatibleDisplay( mWindowManager.getDefaultDisplay().getDisplayId(), ci); } mCompatibilityInfo = ci; } @Override public void addView(View view, android.view.ViewGroup.LayoutParams params) { mWindowManager.addView(view, params, mCompatibilityInfo); } @Override public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) { mWindowManager.updateViewLayout(view, params); } @Override public void removeView(View view) { mWindowManager.removeView(view); } @Override public Display getDefaultDisplay() { return mDefaultDisplay; } @Override public void removeViewImmediate(View view) { mWindowManager.removeViewImmediate(view); } @Override public boolean isHardwareAccelerated() { return mWindowManager.isHardwareAccelerated(); } } public static WindowManagerImpl getDefault() { return sWindowManager; } public static WindowManager getDefault(CompatibilityInfo compatInfo) { CompatibilityInfoHolder cih = new CompatibilityInfoHolder(); cih.set(compatInfo); if (cih.getIfNeeded() == null) { return sWindowManager; } synchronized (sLock) { // NOTE: It would be cleaner to move the implementation of // WindowManagerImpl into a static inner class, and have this // public impl just call into that. Then we can make multiple // instances of WindowManagerImpl for compat mode rather than // having to make wrappers. WindowManager wm = sCompatWindowManagers.get(compatInfo); if (wm == null) { wm = new CompatModeWrapper(sWindowManager, cih); sCompatWindowManagers.put(compatInfo, wm); } return wm; } } public static WindowManager getDefault(CompatibilityInfoHolder compatInfo) { return new CompatModeWrapper(sWindowManager, compatInfo); } public boolean isHardwareAccelerated() { return false; } public void addView(View view) { addView(view, new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE)); } public void addView(View view, ViewGroup.LayoutParams params) { addView(view, params, null, false); } public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) { addView(view, params, cih, false); } private void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih, boolean nest) { if (false) Log.v("WindowManager", "addView view=" + view); if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException( "Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; ViewRootImpl root; View panelParentView = null; synchronized (this) { // Here's an odd/questionable case: if someone tries to add a // view multiple times, then we simply bump up a nesting count // and they need to remove the view the corresponding number of // times to have it actually removed from the window manager. // This is useful specifically for the notification manager, // which can continually add/remove the same view as a // notification gets updated. int index = findViewLocked(view, false); if (index >= 0) { if (!nest) { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } root = mRoots[index]; root.mAddNesting++; // Update layout parameters. view.setLayoutParams(wparams); root.setLayoutParams(wparams, true); return; } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews != null ? mViews.length : 0; for (int i=0; i<count; i++) { if (mRoots[i].mWindow.asBinder() == wparams.token) { panelParentView = mViews[i]; } } } root = new ViewRootImpl(view.getContext()); root.mAddNesting = 1; if (cih == null) { root.mCompatibilityInfo = new CompatibilityInfoHolder(); } else { root.mCompatibilityInfo = cih; } view.setLayoutParams(wparams); if (mViews == null) { index = 1; mViews = new View[1]; mRoots = new ViewRootImpl[1]; mParams = new WindowManager.LayoutParams[1]; } else { index = mViews.length + 1; Object[] old = mViews; mViews = new View[index]; System.arraycopy(old, 0, mViews, 0, index-1); old = mRoots; mRoots = new ViewRootImpl[index]; System.arraycopy(old, 0, mRoots, 0, index-1); old = mParams; mParams = new WindowManager.LayoutParams[index]; System.arraycopy(old, 0, mParams, 0, index-1); } index--; mViews[index] = view; mRoots[index] = root; mParams[index] = wparams; } // do this last because it fires off messages to start doing things root.setView(view, wparams, panelParentView); } public void updateViewLayout(View view, ViewGroup.LayoutParams params) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (this) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots[index]; mParams[index] = wparams; root.setLayoutParams(wparams, false); } } public void removeView(View view) { synchronized (this) { int index = findViewLocked(view, true); View curView = removeViewLocked(index); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } public void removeViewImmediate(View view) { synchronized (this) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots[index]; View curView = root.getView(); root.mAddNesting = 0; root.die(true); finishRemoveViewLocked(curView, index); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } View removeViewLocked(int index) { ViewRootImpl root = mRoots[index]; View view = root.getView(); // Don't really remove until we have matched all calls to add(). root.mAddNesting--; if (root.mAddNesting > 0) { return view; } if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); if (imm != null) { imm.windowDismissed(mViews[index].getWindowToken()); } } root.die(false); finishRemoveViewLocked(view, index); return view; } void finishRemoveViewLocked(View view, int index) { final int count = mViews.length; // remove it from the list View[] tmpViews = new View[count-1]; removeItem(tmpViews, mViews, index); mViews = tmpViews; ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1]; removeItem(tmpRoots, mRoots, index); mRoots = tmpRoots; WindowManager.LayoutParams[] tmpParams = new WindowManager.LayoutParams[count-1]; removeItem(tmpParams, mParams, index); mParams = tmpParams; if (view != null) { view.assignParent(null); // func doesn't allow null... does it matter if we clear them? //view.setLayoutParams(null); } } public void closeAll(IBinder token, String who, String what) { synchronized (this) { if (mViews == null) return; int count = mViews.length; //Log.i("foo", "Closing all windows of " + token); for (int i=0; i<count; i++) { //Log.i("foo", "@ " + i + " token " + mParams[i].token // + " view " + mRoots[i].getView()); if (token == null || mParams[i].token == token) { ViewRootImpl root = mRoots[i]; root.mAddNesting = 1; //Log.i("foo", "Force closing " + root); if (who != null) { WindowLeaked leak = new WindowLeaked( what + " " + who + " has leaked window " + root.getView() + " that was originally added here"); leak.setStackTrace(root.getLocation().getStackTrace()); Log.e("WindowManager", leak.getMessage(), leak); } removeViewLocked(i); i--; count--; } } } } /** * @param level See {@link android.content.ComponentCallbacks} */ public void trimMemory(int level) { if (HardwareRenderer.isAvailable()) { switch (level) { case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: case ComponentCallbacks2.TRIM_MEMORY_MODERATE: // On low and medium end gfx devices if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) { // Force a full memory flush HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); // Destroy all hardware surfaces and resources associated to // known windows synchronized (this) { if (mViews == null) return; int count = mViews.length; for (int i = 0; i < count; i++) { mRoots[i].terminateHardwareResources(); } } // Terminate the hardware renderer to free all resources ManagedEGLContext.doTerminate(); break; } // high end gfx devices fall through to next case default: HardwareRenderer.trimMemory(level); } } } /** * @hide */ public void trimLocalMemory() { synchronized (this) { if (mViews == null) return; int count = mViews.length; for (int i = 0; i < count; i++) { mRoots[i].destroyHardwareLayers(); } } } /** * @hide */ public void dumpGfxInfo(FileDescriptor fd) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new PrintWriter(fout); try { synchronized (this) { if (mViews != null) { pw.println("View hierarchy:"); final int count = mViews.length; int viewsCount = 0; int displayListsSize = 0; int[] info = new int[2]; for (int i = 0; i < count; i++) { ViewRootImpl root = mRoots[i]; root.dumpGfxInfo(pw, info); String name = root.getClass().getName() + '@' + Integer.toHexString(hashCode()); pw.printf(" %s: %d views, %.2f kB (display lists)\n", name, info[0], info[1] / 1024.0f); viewsCount += info[0]; displayListsSize += info[1]; } pw.printf("\nTotal ViewRootImpl: %d\n", count); pw.printf("Total Views: %d\n", viewsCount); pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f); } } } finally { pw.flush(); } } public void setStoppedState(IBinder token, boolean stopped) { synchronized (this) { if (mViews == null) return; int count = mViews.length; for (int i=0; i<count; i++) { if (token == null || mParams[i].token == token) { ViewRootImpl root = mRoots[i]; root.setStopped(stopped); } } } } public void reportNewConfiguration(Configuration config) { synchronized (this) { int count = mViews.length; config = new Configuration(config); for (int i=0; i<count; i++) { ViewRootImpl root = mRoots[i]; root.requestUpdateConfiguration(config); } } } public WindowManager.LayoutParams getRootViewLayoutParameter(View view) { ViewParent vp = view.getParent(); while (vp != null && !(vp instanceof ViewRootImpl)) { vp = vp.getParent(); } if (vp == null) return null; ViewRootImpl vr = (ViewRootImpl)vp; int N = mRoots.length; for (int i = 0; i < N; ++i) { if (mRoots[i] == vr) { return mParams[i]; } } return null; } public void closeAll() { closeAll(null, null, null); } public Display getDefaultDisplay() { return new Display(Display.DEFAULT_DISPLAY, null); } private static void removeItem(Object[] dst, Object[] src, int index) { if (dst.length > 0) { if (index > 0) { System.arraycopy(src, 0, dst, 0, index); } if (index < dst.length) { System.arraycopy(src, index+1, dst, index, src.length-index-1); } } } private int findViewLocked(View view, boolean required) { synchronized (this) { final int count = mViews != null ? mViews.length : 0; for (int i=0; i<count; i++) { if (mViews[i] == view) { return i; } } if (required) { throw new IllegalArgumentException( "View not attached to window manager"); } return -1; } } }