| /* |
| * 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; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.IBinder.DeathRecipient; |
| import android.service.dreams.IDreamService; |
| import android.util.Slog; |
| import android.view.IWindowManager; |
| import android.view.WindowManager; |
| import android.view.WindowManagerGlobal; |
| |
| import com.android.internal.util.DumpUtils; |
| |
| import java.io.PrintWriter; |
| import java.util.NoSuchElementException; |
| |
| /** |
| * Internal controller for starting and stopping the current dream and managing related state. |
| * |
| * Assumes all operations (except {@link #dump}) are called from a single thread. |
| */ |
| final class DreamController { |
| private static final boolean DEBUG = true; |
| private static final String TAG = DreamController.class.getSimpleName(); |
| |
| public interface Listener { |
| void onDreamStopped(boolean wasTest); |
| } |
| |
| private final Context mContext; |
| private final IWindowManager mIWindowManager; |
| private final DeathRecipient mDeathRecipient; |
| private final ServiceConnection mServiceConnection; |
| private final Listener mListener; |
| |
| private Handler mHandler; |
| |
| private ComponentName mCurrentDreamComponent; |
| private IDreamService mCurrentDream; |
| private Binder mCurrentDreamToken; |
| private boolean mCurrentDreamIsTest; |
| |
| public DreamController(Context context, DeathRecipient deathRecipient, |
| ServiceConnection serviceConnection, Listener listener) { |
| mContext = context; |
| mDeathRecipient = deathRecipient; |
| mServiceConnection = serviceConnection; |
| mListener = listener; |
| mIWindowManager = WindowManagerGlobal.getWindowManagerService(); |
| } |
| |
| public void setHandler(Handler handler) { |
| mHandler = handler; |
| } |
| |
| public void dump(PrintWriter pw) { |
| if (mHandler== null || pw == null) { |
| return; |
| } |
| DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { |
| @Override |
| public void dump(PrintWriter pw) { |
| pw.print(" component="); pw.println(mCurrentDreamComponent); |
| pw.print(" token="); pw.println(mCurrentDreamToken); |
| pw.print(" dream="); pw.println(mCurrentDream); |
| } |
| }, pw, 200); |
| } |
| |
| public void start(ComponentName dream, boolean isTest) { |
| if (DEBUG) Slog.v(TAG, String.format("start(%s,%s)", dream, isTest)); |
| |
| if (mCurrentDreamComponent != null ) { |
| if (dream.equals(mCurrentDreamComponent) && isTest == mCurrentDreamIsTest) { |
| if (DEBUG) Slog.v(TAG, "Dream is already started: " + dream); |
| return; |
| } |
| // stop the current dream before starting a new one |
| stop(); |
| } |
| |
| mCurrentDreamComponent = dream; |
| mCurrentDreamIsTest = isTest; |
| mCurrentDreamToken = new Binder(); |
| |
| try { |
| if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken |
| + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM); |
| mIWindowManager.addWindowToken(mCurrentDreamToken, |
| WindowManager.LayoutParams.TYPE_DREAM); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Unable to add window token."); |
| stop(); |
| return; |
| } |
| |
| Intent intent = new Intent(Intent.ACTION_MAIN) |
| .setComponent(mCurrentDreamComponent) |
| .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) |
| .putExtra("android.dreams.TEST", mCurrentDreamIsTest); |
| |
| if (!mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)) { |
| Slog.w(TAG, "Unable to bind service"); |
| stop(); |
| return; |
| } |
| if (DEBUG) Slog.v(TAG, "Bound service"); |
| } |
| |
| public void attach(ComponentName name, IBinder dream) { |
| if (DEBUG) Slog.v(TAG, String.format("attach(%s,%s)", name, dream)); |
| mCurrentDream = IDreamService.Stub.asInterface(dream); |
| |
| boolean linked = linkDeathRecipient(dream); |
| if (!linked) { |
| stop(); |
| return; |
| } |
| |
| try { |
| if (DEBUG) Slog.v(TAG, "Attaching with token:" + mCurrentDreamToken); |
| mCurrentDream.attach(mCurrentDreamToken); |
| } catch (Throwable ex) { |
| Slog.w(TAG, "Unable to send window token to dream:" + ex); |
| stop(); |
| } |
| } |
| |
| public void stop() { |
| if (DEBUG) Slog.v(TAG, "stop()"); |
| |
| if (mCurrentDream != null) { |
| unlinkDeathRecipient(mCurrentDream.asBinder()); |
| |
| if (DEBUG) Slog.v(TAG, "Unbinding: " + mCurrentDreamComponent + " service: " + mCurrentDream); |
| mContext.unbindService(mServiceConnection); |
| } |
| if (mCurrentDreamToken != null) { |
| removeWindowToken(mCurrentDreamToken); |
| } |
| |
| final boolean wasTest = mCurrentDreamIsTest; |
| mCurrentDream = null; |
| mCurrentDreamToken = null; |
| mCurrentDreamComponent = null; |
| mCurrentDreamIsTest = false; |
| |
| if (mListener != null && mHandler != null) { |
| mHandler.post(new Runnable(){ |
| @Override |
| public void run() { |
| mListener.onDreamStopped(wasTest); |
| }}); |
| } |
| } |
| |
| public void stopSelf(IBinder token) { |
| if (DEBUG) Slog.v(TAG, String.format("stopSelf(%s)", token)); |
| if (token == null || token != mCurrentDreamToken) { |
| Slog.w(TAG, "Stop requested for non-current dream token: " + token); |
| } else { |
| stop(); |
| } |
| } |
| |
| private void removeWindowToken(IBinder token) { |
| if (DEBUG) Slog.v(TAG, "Removing window token: " + token); |
| try { |
| mIWindowManager.removeWindowToken(token); |
| } catch (Throwable e) { |
| Slog.w(TAG, "Error removing window token", e); |
| } |
| } |
| |
| private boolean linkDeathRecipient(IBinder dream) { |
| if (DEBUG) Slog.v(TAG, "Linking death recipient"); |
| try { |
| dream.linkToDeath(mDeathRecipient, 0); |
| return true; |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Unable to link death recipient", e); |
| return false; |
| } |
| } |
| |
| private void unlinkDeathRecipient(IBinder dream) { |
| if (DEBUG) Slog.v(TAG, "Unlinking death recipient"); |
| try { |
| dream.unlinkToDeath(mDeathRecipient, 0); |
| } catch (NoSuchElementException e) { |
| // we tried |
| } |
| } |
| |
| } |