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