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