1/*
2 * Copyright (C) 2013 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
17package android.app;
18
19import static android.app.ActivityManager.START_CANCELED;
20
21import android.content.Context;
22import android.content.ContextWrapper;
23import android.content.IIntentSender;
24import android.content.Intent;
25import android.content.IntentSender;
26import android.graphics.SurfaceTexture;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.OperationCanceledException;
30import android.os.RemoteException;
31import android.util.AttributeSet;
32import android.util.DisplayMetrics;
33import android.util.Log;
34import android.view.InputDevice;
35import android.view.InputEvent;
36import android.view.MotionEvent;
37import android.view.Surface;
38import android.view.TextureView;
39import android.view.TextureView.SurfaceTextureListener;
40import android.view.View;
41import android.view.ViewGroup;
42import android.view.WindowManager;
43import dalvik.system.CloseGuard;
44
45import java.lang.ref.WeakReference;
46import java.util.ArrayDeque;
47import java.util.concurrent.Executor;
48import java.util.concurrent.BlockingQueue;
49import java.util.concurrent.LinkedBlockingQueue;
50import java.util.concurrent.ThreadFactory;
51import java.util.concurrent.ThreadPoolExecutor;
52import java.util.concurrent.TimeUnit;
53import java.util.concurrent.atomic.AtomicInteger;
54
55import com.android.internal.annotations.GuardedBy;
56
57
58/** @hide */
59public class ActivityView extends ViewGroup {
60    private static final String TAG = "ActivityView";
61    private static final boolean DEBUG = false;
62
63    private static final int MSG_SET_SURFACE = 1;
64
65    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
66    private static final int MINIMUM_POOL_SIZE = 1;
67    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
68    private static final int KEEP_ALIVE = 1;
69
70    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
71        private final AtomicInteger mCount = new AtomicInteger(1);
72
73        public Thread newThread(Runnable r) {
74            return new Thread(r, "ActivityView #" + mCount.getAndIncrement());
75        }
76    };
77
78    private static final BlockingQueue<Runnable> sPoolWorkQueue =
79            new LinkedBlockingQueue<Runnable>(128);
80
81    /**
82     * An {@link Executor} that can be used to execute tasks in parallel.
83     */
84    private static final Executor sExecutor = new ThreadPoolExecutor(MINIMUM_POOL_SIZE,
85            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
86
87
88    private static class SerialExecutor implements Executor {
89        private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
90        private Runnable mActive;
91
92        public synchronized void execute(final Runnable r) {
93            mTasks.offer(new Runnable() {
94                public void run() {
95                    try {
96                        r.run();
97                    } finally {
98                        scheduleNext();
99                    }
100                }
101            });
102            if (mActive == null) {
103                scheduleNext();
104            }
105        }
106
107        protected synchronized void scheduleNext() {
108            if ((mActive = mTasks.poll()) != null) {
109                sExecutor.execute(mActive);
110            }
111        }
112    }
113
114    private final SerialExecutor mExecutor = new SerialExecutor();
115
116    private final int mDensityDpi;
117    private final TextureView mTextureView;
118
119    @GuardedBy("mActivityContainerLock")
120    private ActivityContainerWrapper mActivityContainer;
121    private Object mActivityContainerLock = new Object();
122
123    private Activity mActivity;
124    private int mWidth;
125    private int mHeight;
126    private Surface mSurface;
127    private int mLastVisibility;
128    private ActivityViewCallback mActivityViewCallback;
129
130
131    public ActivityView(Context context) {
132        this(context, null);
133    }
134
135    public ActivityView(Context context, AttributeSet attrs) {
136        this(context, attrs, 0);
137    }
138
139    public ActivityView(Context context, AttributeSet attrs, int defStyle) {
140        super(context, attrs, defStyle);
141
142        while (context instanceof ContextWrapper) {
143            if (context instanceof Activity) {
144                mActivity = (Activity)context;
145                break;
146            }
147            context = ((ContextWrapper)context).getBaseContext();
148        }
149        if (mActivity == null) {
150            throw new IllegalStateException("The ActivityView's Context is not an Activity.");
151        }
152
153        try {
154            mActivityContainer = new ActivityContainerWrapper(
155                    ActivityManager.getService().createVirtualActivityContainer(
156                            mActivity.getActivityToken(), new ActivityContainerCallback(this)));
157        } catch (RemoteException e) {
158            throw new RuntimeException("ActivityView: Unable to create ActivityContainer. "
159                    + e);
160        }
161
162        mTextureView = new TextureView(context);
163        mTextureView.setSurfaceTextureListener(new ActivityViewSurfaceTextureListener());
164        addView(mTextureView);
165
166        WindowManager wm = (WindowManager)mActivity.getSystemService(Context.WINDOW_SERVICE);
167        DisplayMetrics metrics = new DisplayMetrics();
168        wm.getDefaultDisplay().getMetrics(metrics);
169        mDensityDpi = metrics.densityDpi;
170
171        mLastVisibility = getVisibility();
172
173        if (DEBUG) Log.v(TAG, "ctor()");
174    }
175
176    @Override
177    protected void onLayout(boolean changed, int l, int t, int r, int b) {
178        mTextureView.layout(0, 0, r - l, b - t);
179    }
180
181    @Override
182    protected void onVisibilityChanged(View changedView, final int visibility) {
183        super.onVisibilityChanged(changedView, visibility);
184
185        if (mSurface != null && (visibility == View.GONE || mLastVisibility == View.GONE)) {
186            if (DEBUG) Log.v(TAG, "visibility changed; enqueing runnable");
187            final Surface surface = (visibility == View.GONE) ? null : mSurface;
188            setSurfaceAsync(surface, mWidth, mHeight, mDensityDpi, false);
189        }
190        mLastVisibility = visibility;
191    }
192
193    private boolean injectInputEvent(InputEvent event) {
194        return mActivityContainer != null && mActivityContainer.injectEvent(event);
195    }
196
197    @Override
198    public boolean onTouchEvent(MotionEvent event) {
199        return injectInputEvent(event) || super.onTouchEvent(event);
200    }
201
202    @Override
203    public boolean onGenericMotionEvent(MotionEvent event) {
204        if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
205            if (injectInputEvent(event)) {
206                return true;
207            }
208        }
209        return super.onGenericMotionEvent(event);
210    }
211
212    @Override
213    public void onAttachedToWindow() {
214        if (DEBUG) Log.v(TAG, "onAttachedToWindow(): mActivityContainer=" + mActivityContainer +
215                " mSurface=" + mSurface);
216    }
217
218    @Override
219    public void onDetachedFromWindow() {
220        if (DEBUG) Log.v(TAG, "onDetachedFromWindow(): mActivityContainer=" + mActivityContainer +
221                " mSurface=" + mSurface);
222    }
223
224    public boolean isAttachedToDisplay() {
225        return mSurface != null;
226    }
227
228    public void startActivity(Intent intent) {
229        if (mActivityContainer == null) {
230            throw new IllegalStateException("Attempt to call startActivity after release");
231        }
232        if (mSurface == null) {
233            throw new IllegalStateException("Surface not yet created.");
234        }
235        if (DEBUG) Log.v(TAG, "startActivity(): intent=" + intent + " " +
236                (isAttachedToDisplay() ? "" : "not") + " attached");
237        if (mActivityContainer.startActivity(intent) == START_CANCELED) {
238            throw new OperationCanceledException();
239        }
240    }
241
242    public void startActivity(IntentSender intentSender) {
243        if (mActivityContainer == null) {
244            throw new IllegalStateException("Attempt to call startActivity after release");
245        }
246        if (mSurface == null) {
247            throw new IllegalStateException("Surface not yet created.");
248        }
249        if (DEBUG) Log.v(TAG, "startActivityIntentSender(): intentSender=" + intentSender + " " +
250                (isAttachedToDisplay() ? "" : "not") + " attached");
251        final IIntentSender iIntentSender = intentSender.getTarget();
252        if (mActivityContainer.startActivityIntentSender(iIntentSender) == START_CANCELED) {
253            throw new OperationCanceledException();
254        }
255    }
256
257    public void startActivity(PendingIntent pendingIntent) {
258        if (mActivityContainer == null) {
259            throw new IllegalStateException("Attempt to call startActivity after release");
260        }
261        if (mSurface == null) {
262            throw new IllegalStateException("Surface not yet created.");
263        }
264        if (DEBUG) Log.v(TAG, "startActivityPendingIntent(): PendingIntent=" + pendingIntent + " "
265                + (isAttachedToDisplay() ? "" : "not") + " attached");
266        final IIntentSender iIntentSender = pendingIntent.getTarget();
267        if (mActivityContainer.startActivityIntentSender(iIntentSender) == START_CANCELED) {
268            throw new OperationCanceledException();
269        }
270    }
271
272    public void release() {
273        if (DEBUG) Log.v(TAG, "release() mActivityContainer=" + mActivityContainer +
274                " mSurface=" + mSurface);
275        if (mActivityContainer == null) {
276            Log.e(TAG, "Duplicate call to release");
277            return;
278        }
279        synchronized (mActivityContainerLock) {
280            mActivityContainer.release();
281            mActivityContainer = null;
282        }
283
284        if (mSurface != null) {
285            mSurface.release();
286            mSurface = null;
287        }
288
289        mTextureView.setSurfaceTextureListener(null);
290    }
291
292    private void setSurfaceAsync(final Surface surface, final int width, final int height,
293            final int densityDpi, final boolean callback) {
294        mExecutor.execute(new Runnable() {
295            public void run() {
296                try {
297                    synchronized (mActivityContainerLock) {
298                        if (mActivityContainer != null) {
299                            mActivityContainer.setSurface(surface, width, height, densityDpi);
300                        }
301                    }
302                } catch (RemoteException e) {
303                    throw new RuntimeException(
304                        "ActivityView: Unable to set surface of ActivityContainer. ",
305                        e);
306                }
307                if (callback) {
308                    post(new Runnable() {
309                        @Override
310                        public void run() {
311                            if (mActivityViewCallback != null) {
312                                if (surface != null) {
313                                    mActivityViewCallback.onSurfaceAvailable(ActivityView.this);
314                                } else {
315                                    mActivityViewCallback.onSurfaceDestroyed(ActivityView.this);
316                                }
317                            }
318                        }
319                    });
320                }
321            }
322        });
323    }
324
325    /**
326     * Set the callback to use to report certain state changes.
327     *
328     * Note: If the surface has been created prior to this call being made, then
329     * ActivityViewCallback.onSurfaceAvailable will be called from within setCallback.
330     *
331     *  @param callback The callback to report events to.
332     *
333     * @see ActivityViewCallback
334     */
335    public void setCallback(ActivityViewCallback callback) {
336        mActivityViewCallback = callback;
337
338        if (mSurface != null) {
339            mActivityViewCallback.onSurfaceAvailable(this);
340        }
341    }
342
343    public static abstract class ActivityViewCallback {
344        /**
345         * Called when all activities in the ActivityView have completed and been removed. Register
346         * using {@link ActivityView#setCallback(ActivityViewCallback)}. Each ActivityView may
347         * have at most one callback registered.
348         */
349        public abstract void onAllActivitiesComplete(ActivityView view);
350        /**
351         * Called when the surface is ready to be drawn to. Calling startActivity prior to this
352         * callback will result in an IllegalStateException.
353         */
354        public abstract void onSurfaceAvailable(ActivityView view);
355        /**
356         * Called when the surface has been removed. Calling startActivity after this callback
357         * will result in an IllegalStateException.
358         */
359        public abstract void onSurfaceDestroyed(ActivityView view);
360    }
361
362    private class ActivityViewSurfaceTextureListener implements SurfaceTextureListener {
363        @Override
364        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
365                int height) {
366            if (mActivityContainer == null) {
367                return;
368            }
369            if (DEBUG) Log.d(TAG, "onSurfaceTextureAvailable: width=" + width + " height="
370                    + height);
371            mWidth = width;
372            mHeight = height;
373            mSurface = new Surface(surfaceTexture);
374            setSurfaceAsync(mSurface, mWidth, mHeight, mDensityDpi, true);
375        }
376
377        @Override
378        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
379                int height) {
380            if (mActivityContainer == null) {
381                return;
382            }
383            if (DEBUG) Log.d(TAG, "onSurfaceTextureSizeChanged: w=" + width + " h=" + height);
384        }
385
386        @Override
387        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
388            if (mActivityContainer == null) {
389                return true;
390            }
391            if (DEBUG) Log.d(TAG, "onSurfaceTextureDestroyed");
392            mSurface.release();
393            mSurface = null;
394            setSurfaceAsync(null, mWidth, mHeight, mDensityDpi, true);
395            return true;
396        }
397
398        @Override
399        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
400//            Log.d(TAG, "onSurfaceTextureUpdated");
401        }
402
403    }
404
405    private static class ActivityContainerCallback extends IActivityContainerCallback.Stub {
406        private final WeakReference<ActivityView> mActivityViewWeakReference;
407
408        ActivityContainerCallback(ActivityView activityView) {
409            mActivityViewWeakReference = new WeakReference<>(activityView);
410        }
411
412        @Override
413        public void setVisible(IBinder container, boolean visible) {
414            if (DEBUG) Log.v(TAG, "setVisible(): container=" + container + " visible=" + visible +
415                    " ActivityView=" + mActivityViewWeakReference.get());
416        }
417
418        @Override
419        public void onAllActivitiesComplete(IBinder container) {
420            final ActivityView activityView = mActivityViewWeakReference.get();
421            if (activityView != null) {
422                final ActivityViewCallback callback = activityView.mActivityViewCallback;
423                if (callback != null) {
424                    final WeakReference<ActivityViewCallback> callbackRef =
425                            new WeakReference<>(callback);
426                    activityView.post(new Runnable() {
427                        @Override
428                        public void run() {
429                            ActivityViewCallback callback = callbackRef.get();
430                            if (callback != null) {
431                                callback.onAllActivitiesComplete(activityView);
432                            }
433                        }
434                    });
435                }
436            }
437        }
438    }
439
440    private static class ActivityContainerWrapper {
441        private final IActivityContainer mIActivityContainer;
442        private final CloseGuard mGuard = CloseGuard.get();
443        boolean mOpened; // Protected by mGuard.
444
445        ActivityContainerWrapper(IActivityContainer container) {
446            mIActivityContainer = container;
447            mOpened = true;
448            mGuard.open("release");
449        }
450
451        void setSurface(Surface surface, int width, int height, int density)
452                throws RemoteException {
453            mIActivityContainer.setSurface(surface, width, height, density);
454        }
455
456        int startActivity(Intent intent) {
457            try {
458                return mIActivityContainer.startActivity(intent);
459            } catch (RemoteException e) {
460                throw new RuntimeException("ActivityView: Unable to startActivity. " + e);
461            }
462        }
463
464        int startActivityIntentSender(IIntentSender intentSender) {
465            try {
466                return mIActivityContainer.startActivityIntentSender(intentSender);
467            } catch (RemoteException e) {
468                throw new RuntimeException(
469                        "ActivityView: Unable to startActivity from IntentSender. " + e);
470            }
471        }
472
473        int getDisplayId() {
474            try {
475                return mIActivityContainer.getDisplayId();
476            } catch (RemoteException e) {
477                return -1;
478            }
479        }
480
481        boolean injectEvent(InputEvent event) {
482            try {
483                return mIActivityContainer.injectEvent(event);
484            } catch (RemoteException e) {
485                return false;
486            }
487        }
488
489        void release() {
490            synchronized (mGuard) {
491                if (mOpened) {
492                    if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called");
493                    try {
494                        mIActivityContainer.release();
495                        mGuard.close();
496                    } catch (RemoteException e) {
497                    }
498                    mOpened = false;
499                }
500            }
501        }
502
503        @Override
504        protected void finalize() throws Throwable {
505            if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: finalize called");
506            try {
507                if (mGuard != null) {
508                    mGuard.warnIfOpen();
509                    release();
510                }
511            } finally {
512                super.finalize();
513            }
514        }
515
516    }
517}
518