1/*
2 * Copyright (C) 2011 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 com.android.server.wm;
18
19import com.android.internal.view.IInputContext;
20import com.android.internal.view.IInputMethodClient;
21import com.android.internal.view.IInputMethodManager;
22import com.android.server.wm.WindowManagerService.H;
23
24import android.content.ClipData;
25import android.content.Context;
26import android.content.res.Configuration;
27import android.graphics.Rect;
28import android.graphics.Region;
29import android.os.Binder;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.Parcel;
33import android.os.Process;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.os.UserHandle;
37import android.util.Slog;
38import android.view.Display;
39import android.view.IWindow;
40import android.view.IWindowSession;
41import android.view.InputChannel;
42import android.view.Surface;
43import android.view.SurfaceSession;
44import android.view.WindowManager;
45
46import java.io.PrintWriter;
47
48/**
49 * This class represents an active client session.  There is generally one
50 * Session object per process that is interacting with the window manager.
51 */
52final class Session extends IWindowSession.Stub
53        implements IBinder.DeathRecipient {
54    final WindowManagerService mService;
55    final IInputMethodClient mClient;
56    final IInputContext mInputContext;
57    final int mUid;
58    final int mPid;
59    final String mStringName;
60    SurfaceSession mSurfaceSession;
61    int mNumWindow = 0;
62    boolean mClientDead = false;
63
64    public Session(WindowManagerService service, IInputMethodClient client,
65            IInputContext inputContext) {
66        mService = service;
67        mClient = client;
68        mInputContext = inputContext;
69        mUid = Binder.getCallingUid();
70        mPid = Binder.getCallingPid();
71        StringBuilder sb = new StringBuilder();
72        sb.append("Session{");
73        sb.append(Integer.toHexString(System.identityHashCode(this)));
74        sb.append(" ");
75        sb.append(mPid);
76        if (mUid < Process.FIRST_APPLICATION_UID) {
77            sb.append(":");
78            sb.append(mUid);
79        } else {
80            sb.append(":u");
81            sb.append(UserHandle.getUserId(mUid));
82            sb.append('a');
83            sb.append(UserHandle.getAppId(mUid));
84        }
85        sb.append("}");
86        mStringName = sb.toString();
87
88        synchronized (mService.mWindowMap) {
89            if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
90                IBinder b = ServiceManager.getService(
91                        Context.INPUT_METHOD_SERVICE);
92                mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);
93            }
94        }
95        long ident = Binder.clearCallingIdentity();
96        try {
97            // Note: it is safe to call in to the input method manager
98            // here because we are not holding our lock.
99            if (mService.mInputMethodManager != null) {
100                mService.mInputMethodManager.addClient(client, inputContext,
101                        mUid, mPid);
102            } else {
103                client.setUsingInputMethod(false);
104            }
105            client.asBinder().linkToDeath(this, 0);
106        } catch (RemoteException e) {
107            // The caller has died, so we can just forget about this.
108            try {
109                if (mService.mInputMethodManager != null) {
110                    mService.mInputMethodManager.removeClient(client);
111                }
112            } catch (RemoteException ee) {
113            }
114        } finally {
115            Binder.restoreCallingIdentity(ident);
116        }
117    }
118
119    @Override
120    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
121            throws RemoteException {
122        try {
123            return super.onTransact(code, data, reply, flags);
124        } catch (RuntimeException e) {
125            // Log all 'real' exceptions thrown to the caller
126            if (!(e instanceof SecurityException)) {
127                Slog.e(WindowManagerService.TAG, "Window Session Crash", e);
128            }
129            throw e;
130        }
131    }
132
133    public void binderDied() {
134        // Note: it is safe to call in to the input method manager
135        // here because we are not holding our lock.
136        try {
137            if (mService.mInputMethodManager != null) {
138                mService.mInputMethodManager.removeClient(mClient);
139            }
140        } catch (RemoteException e) {
141        }
142        synchronized(mService.mWindowMap) {
143            mClient.asBinder().unlinkToDeath(this, 0);
144            mClientDead = true;
145            killSessionLocked();
146        }
147    }
148
149    @Override
150    public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
151            int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
152        return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,
153                outContentInsets, outInputChannel);
154    }
155
156    @Override
157    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
158            int viewVisibility, int displayId, Rect outContentInsets,
159            InputChannel outInputChannel) {
160        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
161                outContentInsets, outInputChannel);
162    }
163
164    @Override
165    public int addWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
166            int viewVisibility, Rect outContentInsets) {
167        return addToDisplayWithoutInputChannel(window, seq, attrs, viewVisibility,
168                Display.DEFAULT_DISPLAY, outContentInsets);
169    }
170
171    @Override
172    public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
173            int viewVisibility, int displayId, Rect outContentInsets) {
174        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
175            outContentInsets, null);
176    }
177
178    public void remove(IWindow window) {
179        mService.removeWindow(this, window);
180    }
181
182    public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
183            int requestedWidth, int requestedHeight, int viewFlags,
184            int flags, Rect outFrame, Rect outContentInsets,
185            Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
186        if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from "
187                + Binder.getCallingPid());
188        int res = mService.relayoutWindow(this, window, seq, attrs,
189                requestedWidth, requestedHeight, viewFlags, flags,
190                outFrame, outContentInsets, outVisibleInsets,
191                outConfig, outSurface);
192        if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to "
193                + Binder.getCallingPid());
194        return res;
195    }
196
197    public void performDeferredDestroy(IWindow window) {
198        mService.performDeferredDestroyWindow(this, window);
199    }
200
201    public boolean outOfMemory(IWindow window) {
202        return mService.outOfMemoryWindow(this, window);
203    }
204
205    public void setTransparentRegion(IWindow window, Region region) {
206        mService.setTransparentRegionWindow(this, window, region);
207    }
208
209    public void setInsets(IWindow window, int touchableInsets,
210            Rect contentInsets, Rect visibleInsets, Region touchableArea) {
211        mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
212                visibleInsets, touchableArea);
213    }
214
215    public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
216        mService.getWindowDisplayFrame(this, window, outDisplayFrame);
217    }
218
219    public void finishDrawing(IWindow window) {
220        if (WindowManagerService.localLOGV) Slog.v(
221            WindowManagerService.TAG, "IWindow finishDrawing called for " + window);
222        mService.finishDrawingWindow(this, window);
223    }
224
225    public void setInTouchMode(boolean mode) {
226        synchronized(mService.mWindowMap) {
227            mService.mInTouchMode = mode;
228        }
229    }
230
231    public boolean getInTouchMode() {
232        synchronized(mService.mWindowMap) {
233            return mService.mInTouchMode;
234        }
235    }
236
237    public boolean performHapticFeedback(IWindow window, int effectId,
238            boolean always) {
239        synchronized(mService.mWindowMap) {
240            long ident = Binder.clearCallingIdentity();
241            try {
242                return mService.mPolicy.performHapticFeedbackLw(
243                        mService.windowForClientLocked(this, window, true),
244                        effectId, always);
245            } finally {
246                Binder.restoreCallingIdentity(ident);
247            }
248        }
249    }
250
251    /* Drag/drop */
252    public IBinder prepareDrag(IWindow window, int flags,
253            int width, int height, Surface outSurface) {
254        return mService.prepareDragSurface(window, mSurfaceSession, flags,
255                width, height, outSurface);
256    }
257
258    public boolean performDrag(IWindow window, IBinder dragToken,
259            float touchX, float touchY, float thumbCenterX, float thumbCenterY,
260            ClipData data) {
261        if (WindowManagerService.DEBUG_DRAG) {
262            Slog.d(WindowManagerService.TAG, "perform drag: win=" + window + " data=" + data);
263        }
264
265        synchronized (mService.mWindowMap) {
266            if (mService.mDragState == null) {
267                Slog.w(WindowManagerService.TAG, "No drag prepared");
268                throw new IllegalStateException("performDrag() without prepareDrag()");
269            }
270
271            if (dragToken != mService.mDragState.mToken) {
272                Slog.w(WindowManagerService.TAG, "Performing mismatched drag");
273                throw new IllegalStateException("performDrag() does not match prepareDrag()");
274            }
275
276            WindowState callingWin = mService.windowForClientLocked(null, window, false);
277            if (callingWin == null) {
278                Slog.w(WindowManagerService.TAG, "Bad requesting window " + window);
279                return false;  // !!! TODO: throw here?
280            }
281
282            // !!! TODO: if input is not still focused on the initiating window, fail
283            // the drag initiation (e.g. an alarm window popped up just as the application
284            // called performDrag()
285
286            mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
287
288            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
289            // will let us eliminate the (touchX,touchY) parameters from the API.
290
291            // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
292            // the actual drag event dispatch stuff in the dragstate
293
294            Display display = callingWin.mDisplayContent.getDisplay();
295            mService.mDragState.register(display);
296            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
297            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
298                    mService.mDragState.mServerChannel)) {
299                Slog.e(WindowManagerService.TAG, "Unable to transfer touch focus");
300                mService.mDragState.unregister();
301                mService.mDragState = null;
302                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
303                return false;
304            }
305
306            mService.mDragState.mData = data;
307            mService.mDragState.mCurrentX = touchX;
308            mService.mDragState.mCurrentY = touchY;
309            mService.mDragState.broadcastDragStartedLw(touchX, touchY);
310
311            // remember the thumb offsets for later
312            mService.mDragState.mThumbOffsetX = thumbCenterX;
313            mService.mDragState.mThumbOffsetY = thumbCenterY;
314
315            // Make the surface visible at the proper location
316            final Surface surface = mService.mDragState.mSurface;
317            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
318                    WindowManagerService.TAG, ">>> OPEN TRANSACTION performDrag");
319            Surface.openTransaction();
320            try {
321                surface.setPosition(touchX - thumbCenterX,
322                        touchY - thumbCenterY);
323                surface.setAlpha(.7071f);
324                surface.setLayer(mService.mDragState.getDragLayerLw());
325                surface.setLayerStack(display.getLayerStack());
326                surface.show();
327            } finally {
328                Surface.closeTransaction();
329                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
330                        WindowManagerService.TAG, "<<< CLOSE TRANSACTION performDrag");
331            }
332        }
333
334        return true;    // success!
335    }
336
337    public void reportDropResult(IWindow window, boolean consumed) {
338        IBinder token = window.asBinder();
339        if (WindowManagerService.DEBUG_DRAG) {
340            Slog.d(WindowManagerService.TAG, "Drop result=" + consumed + " reported by " + token);
341        }
342
343        synchronized (mService.mWindowMap) {
344            long ident = Binder.clearCallingIdentity();
345            try {
346                if (mService.mDragState == null) {
347                    // Most likely the drop recipient ANRed and we ended the drag
348                    // out from under it.  Log the issue and move on.
349                    Slog.w(WindowManagerService.TAG, "Drop result given but no drag in progress");
350                    return;
351                }
352
353                if (mService.mDragState.mToken != token) {
354                    // We're in a drag, but the wrong window has responded.
355                    Slog.w(WindowManagerService.TAG, "Invalid drop-result claim by " + window);
356                    throw new IllegalStateException("reportDropResult() by non-recipient");
357                }
358
359                // The right window has responded, even if it's no longer around,
360                // so be sure to halt the timeout even if the later WindowState
361                // lookup fails.
362                mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
363                WindowState callingWin = mService.windowForClientLocked(null, window, false);
364                if (callingWin == null) {
365                    Slog.w(WindowManagerService.TAG, "Bad result-reporting window " + window);
366                    return;  // !!! TODO: throw here?
367                }
368
369                mService.mDragState.mDragResult = consumed;
370                mService.mDragState.endDragLw();
371            } finally {
372                Binder.restoreCallingIdentity(ident);
373            }
374        }
375    }
376
377    public void dragRecipientEntered(IWindow window) {
378        if (WindowManagerService.DEBUG_DRAG) {
379            Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
380        }
381    }
382
383    public void dragRecipientExited(IWindow window) {
384        if (WindowManagerService.DEBUG_DRAG) {
385            Slog.d(WindowManagerService.TAG, "Drag from old candidate view @ " + window.asBinder());
386        }
387    }
388
389    public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
390        synchronized(mService.mWindowMap) {
391            long ident = Binder.clearCallingIdentity();
392            try {
393                mService.setWindowWallpaperPositionLocked(
394                        mService.windowForClientLocked(this, window, true),
395                        x, y, xStep, yStep);
396            } finally {
397                Binder.restoreCallingIdentity(ident);
398            }
399        }
400    }
401
402    public void wallpaperOffsetsComplete(IBinder window) {
403        mService.wallpaperOffsetsComplete(window);
404    }
405
406    public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
407            int z, Bundle extras, boolean sync) {
408        synchronized(mService.mWindowMap) {
409            long ident = Binder.clearCallingIdentity();
410            try {
411                return mService.sendWindowWallpaperCommandLocked(
412                        mService.windowForClientLocked(this, window, true),
413                        action, x, y, z, extras, sync);
414            } finally {
415                Binder.restoreCallingIdentity(ident);
416            }
417        }
418    }
419
420    public void wallpaperCommandComplete(IBinder window, Bundle result) {
421        mService.wallpaperCommandComplete(window, result);
422    }
423
424    public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
425            float dsdx, float dtdx, float dsdy, float dtdy) {
426        synchronized(mService.mWindowMap) {
427            long ident = Binder.clearCallingIdentity();
428            try {
429                mService.setUniverseTransformLocked(
430                        mService.windowForClientLocked(this, window, true),
431                        alpha, offx, offy, dsdx, dtdx, dsdy, dtdy);
432            } finally {
433                Binder.restoreCallingIdentity(ident);
434            }
435        }
436    }
437
438    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
439        synchronized(mService.mWindowMap) {
440            final long identity = Binder.clearCallingIdentity();
441            try {
442                mService.onRectangleOnScreenRequested(token, rectangle, immediate);
443            } finally {
444                Binder.restoreCallingIdentity(identity);
445            }
446        }
447    }
448
449    void windowAddedLocked() {
450        if (mSurfaceSession == null) {
451            if (WindowManagerService.localLOGV) Slog.v(
452                WindowManagerService.TAG, "First window added to " + this + ", creating SurfaceSession");
453            mSurfaceSession = new SurfaceSession();
454            if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
455                    WindowManagerService.TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
456            mService.mSessions.add(this);
457        }
458        mNumWindow++;
459    }
460
461    void windowRemovedLocked() {
462        mNumWindow--;
463        killSessionLocked();
464    }
465
466    void killSessionLocked() {
467        if (mNumWindow <= 0 && mClientDead) {
468            mService.mSessions.remove(this);
469            if (mSurfaceSession != null) {
470                if (WindowManagerService.localLOGV) Slog.v(
471                    WindowManagerService.TAG, "Last window removed from " + this
472                    + ", destroying " + mSurfaceSession);
473                if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
474                        WindowManagerService.TAG, "  KILL SURFACE SESSION " + mSurfaceSession);
475                try {
476                    mSurfaceSession.kill();
477                } catch (Exception e) {
478                    Slog.w(WindowManagerService.TAG, "Exception thrown when killing surface session "
479                        + mSurfaceSession + " in session " + this
480                        + ": " + e.toString());
481                }
482                mSurfaceSession = null;
483            }
484        }
485    }
486
487    void dump(PrintWriter pw, String prefix) {
488        pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
489                pw.print(" mClientDead="); pw.print(mClientDead);
490                pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
491    }
492
493    @Override
494    public String toString() {
495        return mStringName;
496    }
497}