Home | History | Annotate | Download | only in dreams
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.dreams;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.os.Binder;
     24 import android.os.Handler;
     25 import android.os.IBinder;
     26 import android.os.RemoteException;
     27 import android.os.IBinder.DeathRecipient;
     28 import android.os.Trace;
     29 import android.os.UserHandle;
     30 import android.service.dreams.DreamService;
     31 import android.service.dreams.IDreamService;
     32 import android.util.Slog;
     33 import android.view.IWindowManager;
     34 import android.view.WindowManager;
     35 import android.view.WindowManagerGlobal;
     36 
     37 import java.io.PrintWriter;
     38 import java.util.NoSuchElementException;
     39 
     40 /**
     41  * Internal controller for starting and stopping the current dream and managing related state.
     42  *
     43  * Assumes all operations are called from the dream handler thread.
     44  */
     45 final class DreamController {
     46     private static final String TAG = "DreamController";
     47 
     48     // How long we wait for a newly bound dream to create the service connection
     49     private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000;
     50 
     51     // Time to allow the dream to perform an exit transition when waking up.
     52     private static final int DREAM_FINISH_TIMEOUT = 5 * 1000;
     53 
     54     private final Context mContext;
     55     private final Handler mHandler;
     56     private final Listener mListener;
     57     private final IWindowManager mIWindowManager;
     58 
     59     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
     60             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
     61     private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
     62             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
     63 
     64     private final Intent mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
     65 
     66     private DreamRecord mCurrentDream;
     67 
     68     private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
     69         @Override
     70         public void run() {
     71             if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
     72                 Slog.w(TAG, "Bound dream did not connect in the time allotted");
     73                 stopDream(true /*immediate*/);
     74             }
     75         }
     76     };
     77 
     78     private final Runnable mStopStubbornDreamRunnable = new Runnable() {
     79         @Override
     80         public void run() {
     81             Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
     82             stopDream(true /*immediate*/);
     83         }
     84     };
     85 
     86     public DreamController(Context context, Handler handler, Listener listener) {
     87         mContext = context;
     88         mHandler = handler;
     89         mListener = listener;
     90         mIWindowManager = WindowManagerGlobal.getWindowManagerService();
     91     }
     92 
     93     public void dump(PrintWriter pw) {
     94         pw.println("Dreamland:");
     95         if (mCurrentDream != null) {
     96             pw.println("  mCurrentDream:");
     97             pw.println("    mToken=" + mCurrentDream.mToken);
     98             pw.println("    mName=" + mCurrentDream.mName);
     99             pw.println("    mIsTest=" + mCurrentDream.mIsTest);
    100             pw.println("    mCanDoze=" + mCurrentDream.mCanDoze);
    101             pw.println("    mUserId=" + mCurrentDream.mUserId);
    102             pw.println("    mBound=" + mCurrentDream.mBound);
    103             pw.println("    mService=" + mCurrentDream.mService);
    104             pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
    105             pw.println("    mWakingGently=" + mCurrentDream.mWakingGently);
    106         } else {
    107             pw.println("  mCurrentDream: null");
    108         }
    109     }
    110 
    111     public void startDream(Binder token, ComponentName name,
    112             boolean isTest, boolean canDoze, int userId) {
    113         stopDream(true /*immediate*/);
    114 
    115         Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
    116         try {
    117             // Close the notification shade. Don't need to send to all, but better to be explicit.
    118             mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
    119 
    120             Slog.i(TAG, "Starting dream: name=" + name
    121                     + ", isTest=" + isTest + ", canDoze=" + canDoze
    122                     + ", userId=" + userId);
    123 
    124             mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId);
    125 
    126             try {
    127                 mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
    128             } catch (RemoteException ex) {
    129                 Slog.e(TAG, "Unable to add window token for dream.", ex);
    130                 stopDream(true /*immediate*/);
    131                 return;
    132             }
    133 
    134             Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
    135             intent.setComponent(name);
    136             intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    137             try {
    138                 if (!mContext.bindServiceAsUser(intent, mCurrentDream,
    139                         Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
    140                     Slog.e(TAG, "Unable to bind dream service: " + intent);
    141                     stopDream(true /*immediate*/);
    142                     return;
    143                 }
    144             } catch (SecurityException ex) {
    145                 Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
    146                 stopDream(true /*immediate*/);
    147                 return;
    148             }
    149 
    150             mCurrentDream.mBound = true;
    151             mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
    152         } finally {
    153             Trace.traceEnd(Trace.TRACE_TAG_POWER);
    154         }
    155     }
    156 
    157     public void stopDream(boolean immediate) {
    158         if (mCurrentDream == null) {
    159             return;
    160         }
    161 
    162         Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
    163         try {
    164             if (!immediate) {
    165                 if (mCurrentDream.mWakingGently) {
    166                     return; // already waking gently
    167                 }
    168 
    169                 if (mCurrentDream.mService != null) {
    170                     // Give the dream a moment to wake up and finish itself gently.
    171                     mCurrentDream.mWakingGently = true;
    172                     try {
    173                         mCurrentDream.mService.wakeUp();
    174                         mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
    175                         return;
    176                     } catch (RemoteException ex) {
    177                         // oh well, we tried, finish immediately instead
    178                     }
    179                 }
    180             }
    181 
    182             final DreamRecord oldDream = mCurrentDream;
    183             mCurrentDream = null;
    184             Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
    185                     + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze
    186                     + ", userId=" + oldDream.mUserId);
    187 
    188             mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
    189             mHandler.removeCallbacks(mStopStubbornDreamRunnable);
    190 
    191             if (oldDream.mSentStartBroadcast) {
    192                 mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
    193             }
    194 
    195             if (oldDream.mService != null) {
    196                 // Tell the dream that it's being stopped so that
    197                 // it can shut down nicely before we yank its window token out from
    198                 // under it.
    199                 try {
    200                     oldDream.mService.detach();
    201                 } catch (RemoteException ex) {
    202                     // we don't care; this thing is on the way out
    203                 }
    204 
    205                 try {
    206                     oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
    207                 } catch (NoSuchElementException ex) {
    208                     // don't care
    209                 }
    210                 oldDream.mService = null;
    211             }
    212 
    213             if (oldDream.mBound) {
    214                 mContext.unbindService(oldDream);
    215             }
    216 
    217             try {
    218                 mIWindowManager.removeWindowToken(oldDream.mToken);
    219             } catch (RemoteException ex) {
    220                 Slog.w(TAG, "Error removing window token for dream.", ex);
    221             }
    222 
    223             mHandler.post(new Runnable() {
    224                 @Override
    225                 public void run() {
    226                     mListener.onDreamStopped(oldDream.mToken);
    227                 }
    228             });
    229         } finally {
    230             Trace.traceEnd(Trace.TRACE_TAG_POWER);
    231         }
    232     }
    233 
    234     private void attach(IDreamService service) {
    235         try {
    236             service.asBinder().linkToDeath(mCurrentDream, 0);
    237             service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze);
    238         } catch (RemoteException ex) {
    239             Slog.e(TAG, "The dream service died unexpectedly.", ex);
    240             stopDream(true /*immediate*/);
    241             return;
    242         }
    243 
    244         mCurrentDream.mService = service;
    245 
    246         if (!mCurrentDream.mIsTest) {
    247             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
    248             mCurrentDream.mSentStartBroadcast = true;
    249         }
    250     }
    251 
    252     /**
    253      * Callback interface to be implemented by the {@link DreamManagerService}.
    254      */
    255     public interface Listener {
    256         void onDreamStopped(Binder token);
    257     }
    258 
    259     private final class DreamRecord implements DeathRecipient, ServiceConnection {
    260         public final Binder mToken;
    261         public final ComponentName mName;
    262         public final boolean mIsTest;
    263         public final boolean mCanDoze;
    264         public final int mUserId;
    265 
    266         public boolean mBound;
    267         public boolean mConnected;
    268         public IDreamService mService;
    269         public boolean mSentStartBroadcast;
    270 
    271         public boolean mWakingGently;
    272 
    273         public DreamRecord(Binder token, ComponentName name,
    274                 boolean isTest, boolean canDoze, int userId) {
    275             mToken = token;
    276             mName = name;
    277             mIsTest = isTest;
    278             mCanDoze = canDoze;
    279             mUserId  = userId;
    280         }
    281 
    282         // May be called on any thread.
    283         @Override
    284         public void binderDied() {
    285             mHandler.post(new Runnable() {
    286                 @Override
    287                 public void run() {
    288                     mService = null;
    289                     if (mCurrentDream == DreamRecord.this) {
    290                         stopDream(true /*immediate*/);
    291                     }
    292                 }
    293             });
    294         }
    295 
    296         // May be called on any thread.
    297         @Override
    298         public void onServiceConnected(ComponentName name, final IBinder service) {
    299             mHandler.post(new Runnable() {
    300                 @Override
    301                 public void run() {
    302                     mConnected = true;
    303                     if (mCurrentDream == DreamRecord.this && mService == null) {
    304                         attach(IDreamService.Stub.asInterface(service));
    305                     }
    306                 }
    307             });
    308         }
    309 
    310         // May be called on any thread.
    311         @Override
    312         public void onServiceDisconnected(ComponentName name) {
    313             mHandler.post(new Runnable() {
    314                 @Override
    315                 public void run() {
    316                     mService = null;
    317                     if (mCurrentDream == DreamRecord.this) {
    318                         stopDream(true /*immediate*/);
    319                     }
    320                 }
    321             });
    322         }
    323     }
    324 }