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.UserHandle;
     29 import android.service.dreams.DreamService;
     30 import android.service.dreams.IDreamService;
     31 import android.util.Slog;
     32 import android.view.IWindowManager;
     33 import android.view.WindowManager;
     34 import android.view.WindowManagerGlobal;
     35 
     36 import java.io.PrintWriter;
     37 import java.util.NoSuchElementException;
     38 
     39 /**
     40  * Internal controller for starting and stopping the current dream and managing related state.
     41  *
     42  * Assumes all operations are called from the dream handler thread.
     43  */
     44 final class DreamController {
     45     private static final String TAG = "DreamController";
     46 
     47     // How long we wait for a newly bound dream to create the service connection
     48     private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000;
     49 
     50     private final Context mContext;
     51     private final Handler mHandler;
     52     private final Listener mListener;
     53     private final IWindowManager mIWindowManager;
     54 
     55     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
     56             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
     57     private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
     58             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
     59 
     60     private final Intent mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
     61 
     62     private DreamRecord mCurrentDream;
     63 
     64     private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
     65         @Override
     66         public void run() {
     67             if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
     68                 Slog.w(TAG, "Bound dream did not connect in the time allotted");
     69                 stopDream();
     70             }
     71         }
     72     };
     73 
     74     public DreamController(Context context, Handler handler, Listener listener) {
     75         mContext = context;
     76         mHandler = handler;
     77         mListener = listener;
     78         mIWindowManager = WindowManagerGlobal.getWindowManagerService();
     79     }
     80 
     81     public void dump(PrintWriter pw) {
     82         pw.println("Dreamland:");
     83         if (mCurrentDream != null) {
     84             pw.println("  mCurrentDream:");
     85             pw.println("    mToken=" + mCurrentDream.mToken);
     86             pw.println("    mName=" + mCurrentDream.mName);
     87             pw.println("    mIsTest=" + mCurrentDream.mIsTest);
     88             pw.println("    mUserId=" + mCurrentDream.mUserId);
     89             pw.println("    mBound=" + mCurrentDream.mBound);
     90             pw.println("    mService=" + mCurrentDream.mService);
     91             pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
     92         } else {
     93             pw.println("  mCurrentDream: null");
     94         }
     95     }
     96 
     97     public void startDream(Binder token, ComponentName name, boolean isTest, int userId) {
     98         stopDream();
     99 
    100         // Close the notification shade. Don't need to send to all, but better to be explicit.
    101         mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
    102 
    103         Slog.i(TAG, "Starting dream: name=" + name + ", isTest=" + isTest + ", userId=" + userId);
    104 
    105         mCurrentDream = new DreamRecord(token, name, isTest, userId);
    106 
    107         try {
    108             mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
    109         } catch (RemoteException ex) {
    110             Slog.e(TAG, "Unable to add window token for dream.", ex);
    111             stopDream();
    112             return;
    113         }
    114 
    115         Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
    116         intent.setComponent(name);
    117         intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    118         try {
    119             if (!mContext.bindServiceAsUser(intent, mCurrentDream,
    120                     Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
    121                 Slog.e(TAG, "Unable to bind dream service: " + intent);
    122                 stopDream();
    123                 return;
    124             }
    125         } catch (SecurityException ex) {
    126             Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
    127             stopDream();
    128             return;
    129         }
    130 
    131         mCurrentDream.mBound = true;
    132         mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
    133     }
    134 
    135     public void stopDream() {
    136         if (mCurrentDream == null) {
    137             return;
    138         }
    139 
    140         final DreamRecord oldDream = mCurrentDream;
    141         mCurrentDream = null;
    142         Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
    143                 + ", isTest=" + oldDream.mIsTest + ", userId=" + oldDream.mUserId);
    144 
    145         mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
    146 
    147         if (oldDream.mSentStartBroadcast) {
    148             mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
    149         }
    150 
    151         if (oldDream.mService != null) {
    152             // Tell the dream that it's being stopped so that
    153             // it can shut down nicely before we yank its window token out from
    154             // under it.
    155             try {
    156                 oldDream.mService.detach();
    157             } catch (RemoteException ex) {
    158                 // we don't care; this thing is on the way out
    159             }
    160 
    161             try {
    162                 oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
    163             } catch (NoSuchElementException ex) {
    164                 // don't care
    165             }
    166             oldDream.mService = null;
    167         }
    168 
    169         if (oldDream.mBound) {
    170             mContext.unbindService(oldDream);
    171         }
    172 
    173         try {
    174             mIWindowManager.removeWindowToken(oldDream.mToken);
    175         } catch (RemoteException ex) {
    176             Slog.w(TAG, "Error removing window token for dream.", ex);
    177         }
    178 
    179         mHandler.post(new Runnable() {
    180             @Override
    181             public void run() {
    182                 mListener.onDreamStopped(oldDream.mToken);
    183             }
    184         });
    185     }
    186 
    187     private void attach(IDreamService service) {
    188         try {
    189             service.asBinder().linkToDeath(mCurrentDream, 0);
    190             service.attach(mCurrentDream.mToken);
    191         } catch (RemoteException ex) {
    192             Slog.e(TAG, "The dream service died unexpectedly.", ex);
    193             stopDream();
    194             return;
    195         }
    196 
    197         mCurrentDream.mService = service;
    198 
    199         if (!mCurrentDream.mIsTest) {
    200             mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
    201             mCurrentDream.mSentStartBroadcast = true;
    202         }
    203     }
    204 
    205     /**
    206      * Callback interface to be implemented by the {@link DreamManagerService}.
    207      */
    208     public interface Listener {
    209         void onDreamStopped(Binder token);
    210     }
    211 
    212     private final class DreamRecord implements DeathRecipient, ServiceConnection {
    213         public final Binder mToken;
    214         public final ComponentName mName;
    215         public final boolean mIsTest;
    216         public final int mUserId;
    217 
    218         public boolean mBound;
    219         public boolean mConnected;
    220         public IDreamService mService;
    221         public boolean mSentStartBroadcast;
    222 
    223         public DreamRecord(Binder token, ComponentName name,
    224                 boolean isTest, int userId) {
    225             mToken = token;
    226             mName = name;
    227             mIsTest = isTest;
    228             mUserId  = userId;
    229         }
    230 
    231         // May be called on any thread.
    232         @Override
    233         public void binderDied() {
    234             mHandler.post(new Runnable() {
    235                 @Override
    236                 public void run() {
    237                     mService = null;
    238                     if (mCurrentDream == DreamRecord.this) {
    239                         stopDream();
    240                     }
    241                 }
    242             });
    243         }
    244 
    245         // May be called on any thread.
    246         @Override
    247         public void onServiceConnected(ComponentName name, final IBinder service) {
    248             mHandler.post(new Runnable() {
    249                 @Override
    250                 public void run() {
    251                     mConnected = true;
    252                     if (mCurrentDream == DreamRecord.this && mService == null) {
    253                         attach(IDreamService.Stub.asInterface(service));
    254                     }
    255                 }
    256             });
    257         }
    258 
    259         // May be called on any thread.
    260         @Override
    261         public void onServiceDisconnected(ComponentName name) {
    262             mHandler.post(new Runnable() {
    263                 @Override
    264                 public void run() {
    265                     mService = null;
    266                     if (mCurrentDream == DreamRecord.this) {
    267                         stopDream();
    268                     }
    269                 }
    270             });
    271         }
    272     }
    273 }