| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.dreams; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.MetricsProto.MetricsEvent; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.IRemoteCallback; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.IBinder.DeathRecipient; |
| import android.os.SystemClock; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.service.dreams.DreamService; |
| import android.service.dreams.IDreamService; |
| import android.util.Slog; |
| import android.view.IWindowManager; |
| import android.view.WindowManager; |
| import android.view.WindowManagerGlobal; |
| |
| import java.io.PrintWriter; |
| import java.util.NoSuchElementException; |
| |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.view.WindowManager.LayoutParams.TYPE_DREAM; |
| |
| /** |
| * Internal controller for starting and stopping the current dream and managing related state. |
| * |
| * Assumes all operations are called from the dream handler thread. |
| */ |
| final class DreamController { |
| private static final String TAG = "DreamController"; |
| |
| // How long we wait for a newly bound dream to create the service connection |
| private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000; |
| |
| // Time to allow the dream to perform an exit transition when waking up. |
| private static final int DREAM_FINISH_TIMEOUT = 5 * 1000; |
| |
| private final Context mContext; |
| private final Handler mHandler; |
| private final Listener mListener; |
| private final IWindowManager mIWindowManager; |
| private long mDreamStartTime; |
| |
| private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) |
| .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED) |
| .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); |
| |
| private final Intent mCloseNotificationShadeIntent; |
| |
| private DreamRecord mCurrentDream; |
| |
| private final Runnable mStopUnconnectedDreamRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) { |
| Slog.w(TAG, "Bound dream did not connect in the time allotted"); |
| stopDream(true /*immediate*/); |
| } |
| } |
| }; |
| |
| private final Runnable mStopStubbornDreamRunnable = new Runnable() { |
| @Override |
| public void run() { |
| Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); |
| stopDream(true /*immediate*/); |
| } |
| }; |
| |
| public DreamController(Context context, Handler handler, Listener listener) { |
| mContext = context; |
| mHandler = handler; |
| mListener = listener; |
| mIWindowManager = WindowManagerGlobal.getWindowManagerService(); |
| mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| mCloseNotificationShadeIntent.putExtra("reason", "dream"); |
| } |
| |
| public void dump(PrintWriter pw) { |
| pw.println("Dreamland:"); |
| if (mCurrentDream != null) { |
| pw.println(" mCurrentDream:"); |
| pw.println(" mToken=" + mCurrentDream.mToken); |
| pw.println(" mName=" + mCurrentDream.mName); |
| pw.println(" mIsTest=" + mCurrentDream.mIsTest); |
| pw.println(" mCanDoze=" + mCurrentDream.mCanDoze); |
| pw.println(" mUserId=" + mCurrentDream.mUserId); |
| pw.println(" mBound=" + mCurrentDream.mBound); |
| pw.println(" mService=" + mCurrentDream.mService); |
| pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); |
| pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); |
| } else { |
| pw.println(" mCurrentDream: null"); |
| } |
| } |
| |
| public void startDream(Binder token, ComponentName name, |
| boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { |
| stopDream(true /*immediate*/); |
| |
| Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); |
| try { |
| // Close the notification shade. No need to send to all, but better to be explicit. |
| mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); |
| |
| Slog.i(TAG, "Starting dream: name=" + name |
| + ", isTest=" + isTest + ", canDoze=" + canDoze |
| + ", userId=" + userId); |
| |
| mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId, wakeLock); |
| |
| mDreamStartTime = SystemClock.elapsedRealtime(); |
| MetricsLogger.visible(mContext, |
| mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); |
| |
| try { |
| mIWindowManager.addWindowToken(token, TYPE_DREAM, DEFAULT_DISPLAY); |
| } catch (RemoteException ex) { |
| Slog.e(TAG, "Unable to add window token for dream.", ex); |
| stopDream(true /*immediate*/); |
| return; |
| } |
| |
| Intent intent = new Intent(DreamService.SERVICE_INTERFACE); |
| intent.setComponent(name); |
| intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); |
| try { |
| if (!mContext.bindServiceAsUser(intent, mCurrentDream, |
| Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, |
| new UserHandle(userId))) { |
| Slog.e(TAG, "Unable to bind dream service: " + intent); |
| stopDream(true /*immediate*/); |
| return; |
| } |
| } catch (SecurityException ex) { |
| Slog.e(TAG, "Unable to bind dream service: " + intent, ex); |
| stopDream(true /*immediate*/); |
| return; |
| } |
| |
| mCurrentDream.mBound = true; |
| mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_POWER); |
| } |
| } |
| |
| public void stopDream(boolean immediate) { |
| if (mCurrentDream == null) { |
| return; |
| } |
| |
| Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); |
| try { |
| if (!immediate) { |
| if (mCurrentDream.mWakingGently) { |
| return; // already waking gently |
| } |
| |
| if (mCurrentDream.mService != null) { |
| // Give the dream a moment to wake up and finish itself gently. |
| mCurrentDream.mWakingGently = true; |
| try { |
| mCurrentDream.mService.wakeUp(); |
| mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); |
| return; |
| } catch (RemoteException ex) { |
| // oh well, we tried, finish immediately instead |
| } |
| } |
| } |
| |
| final DreamRecord oldDream = mCurrentDream; |
| mCurrentDream = null; |
| Slog.i(TAG, "Stopping dream: name=" + oldDream.mName |
| + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze |
| + ", userId=" + oldDream.mUserId); |
| MetricsLogger.hidden(mContext, |
| oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); |
| MetricsLogger.histogram(mContext, |
| oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" , |
| (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L))); |
| |
| mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); |
| mHandler.removeCallbacks(mStopStubbornDreamRunnable); |
| |
| if (oldDream.mSentStartBroadcast) { |
| mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); |
| } |
| |
| if (oldDream.mService != null) { |
| // Tell the dream that it's being stopped so that |
| // it can shut down nicely before we yank its window token out from |
| // under it. |
| try { |
| oldDream.mService.detach(); |
| } catch (RemoteException ex) { |
| // we don't care; this thing is on the way out |
| } |
| |
| try { |
| oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); |
| } catch (NoSuchElementException ex) { |
| // don't care |
| } |
| oldDream.mService = null; |
| } |
| |
| if (oldDream.mBound) { |
| mContext.unbindService(oldDream); |
| } |
| oldDream.releaseWakeLockIfNeeded(); |
| |
| try { |
| mIWindowManager.removeWindowToken(oldDream.mToken, DEFAULT_DISPLAY); |
| } catch (RemoteException ex) { |
| Slog.w(TAG, "Error removing window token for dream.", ex); |
| } |
| |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mListener.onDreamStopped(oldDream.mToken); |
| } |
| }); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_POWER); |
| } |
| } |
| |
| private void attach(IDreamService service) { |
| try { |
| service.asBinder().linkToDeath(mCurrentDream, 0); |
| service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze, |
| mCurrentDream.mDreamingStartedCallback); |
| } catch (RemoteException ex) { |
| Slog.e(TAG, "The dream service died unexpectedly.", ex); |
| stopDream(true /*immediate*/); |
| return; |
| } |
| |
| mCurrentDream.mService = service; |
| |
| if (!mCurrentDream.mIsTest) { |
| mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); |
| mCurrentDream.mSentStartBroadcast = true; |
| } |
| } |
| |
| /** |
| * Callback interface to be implemented by the {@link DreamManagerService}. |
| */ |
| public interface Listener { |
| void onDreamStopped(Binder token); |
| } |
| |
| private final class DreamRecord implements DeathRecipient, ServiceConnection { |
| public final Binder mToken; |
| public final ComponentName mName; |
| public final boolean mIsTest; |
| public final boolean mCanDoze; |
| public final int mUserId; |
| |
| public PowerManager.WakeLock mWakeLock; |
| public boolean mBound; |
| public boolean mConnected; |
| public IDreamService mService; |
| public boolean mSentStartBroadcast; |
| |
| public boolean mWakingGently; |
| |
| public DreamRecord(Binder token, ComponentName name, |
| boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { |
| mToken = token; |
| mName = name; |
| mIsTest = isTest; |
| mCanDoze = canDoze; |
| mUserId = userId; |
| mWakeLock = wakeLock; |
| // Hold the lock while we're waiting for the service to connect and start dreaming. |
| // Released after the service has started dreaming, we stop dreaming, or it timed out. |
| mWakeLock.acquire(); |
| mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); |
| } |
| |
| // May be called on any thread. |
| @Override |
| public void binderDied() { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mService = null; |
| if (mCurrentDream == DreamRecord.this) { |
| stopDream(true /*immediate*/); |
| } |
| } |
| }); |
| } |
| |
| // May be called on any thread. |
| @Override |
| public void onServiceConnected(ComponentName name, final IBinder service) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mConnected = true; |
| if (mCurrentDream == DreamRecord.this && mService == null) { |
| attach(IDreamService.Stub.asInterface(service)); |
| // Wake lock will be released once dreaming starts. |
| } else { |
| releaseWakeLockIfNeeded(); |
| } |
| } |
| }); |
| } |
| |
| // May be called on any thread. |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mService = null; |
| if (mCurrentDream == DreamRecord.this) { |
| stopDream(true /*immediate*/); |
| } |
| } |
| }); |
| } |
| |
| void releaseWakeLockIfNeeded() { |
| if (mWakeLock != null) { |
| mWakeLock.release(); |
| mWakeLock = null; |
| mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); |
| } |
| } |
| |
| final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; |
| |
| final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { |
| // May be called on any thread. |
| @Override |
| public void sendResult(Bundle data) throws RemoteException { |
| mHandler.post(mReleaseWakeLockIfNeeded); |
| } |
| }; |
| } |
| } |