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