blob: 61dca4c1a37bc2927e1aa0e757025383297ca872 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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.view;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.PixelFormat;
22import android.graphics.PorterDuff;
23import android.graphics.Rect;
24import android.graphics.Region;
25import android.os.Handler;
26import android.os.Message;
27import android.os.RemoteException;
28import android.os.SystemClock;
29import android.os.ParcelFileDescriptor;
30import android.util.AttributeSet;
31import android.util.Config;
32import android.util.Log;
33import java.util.ArrayList;
34
35import java.util.concurrent.locks.ReentrantLock;
36import java.lang.ref.WeakReference;
37
38/**
39 * Provides a dedicated drawing surface embedded inside of a view hierarchy.
40 * You can control the format of this surface and, if you like, its size; the
41 * SurfaceView takes care of placing the surface at the correct location on the
42 * screen
43 *
44 * <p>The surface is Z ordered so that it is behind the window holding its
45 * SurfaceView; the SurfaceView punches a hole in its window to allow its
46 * surface to be displayed. The view hierarchy will take care of correctly
47 * compositing with the Surface any siblings of the SurfaceView that would
48 * normally appear on top of it. This can be used to place overlays such as
49 * buttons on top of the Surface, though note however that it can have an
50 * impact on performance since a full alpha-blended composite will be performed
51 * each time the Surface changes.
52 *
53 * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
54 * which can be retrieved by calling {@link #getHolder}.
55 *
56 * <p>The Surface will be created for you while the SurfaceView's window is
57 * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
58 * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
59 * Surface is created and destroyed as the window is shown and hidden.
60 *
61 * <p>One of the purposes of this class is to provide a surface in which a
62 * secondary thread can render in to the screen. If you are going to use it
63 * this way, you need to be aware of some threading semantics:
64 *
65 * <ul>
66 * <li> All SurfaceView and
67 * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
68 * from the thread running the SurfaceView's window (typically the main thread
69 * of the application). They thus need to correctly synchronize with any
70 * state that is also touched by the drawing thread.
71 * <li> You must ensure that the drawing thread only touches the underlying
72 * Surface while it is valid -- between
73 * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
74 * and
75 * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
76 * </ul>
77 */
78public class SurfaceView extends View {
79 static private final String TAG = "SurfaceView";
80 static private final boolean DEBUG = false;
81 static private final boolean localLOGV = DEBUG ? true : Config.LOGV;
82
83 final ArrayList<SurfaceHolder.Callback> mCallbacks
84 = new ArrayList<SurfaceHolder.Callback>();
85
86 final int[] mLocation = new int[2];
87
88 final ReentrantLock mSurfaceLock = new ReentrantLock();
89 final Surface mSurface = new Surface();
90 boolean mDrawingStopped = true;
91
92 final WindowManager.LayoutParams mLayout
93 = new WindowManager.LayoutParams();
94 IWindowSession mSession;
95 MyWindow mWindow;
96 final Rect mVisibleInsets = new Rect();
97 final Rect mWinFrame = new Rect();
98 final Rect mContentInsets = new Rect();
99
100 static final int KEEP_SCREEN_ON_MSG = 1;
101 static final int GET_NEW_SURFACE_MSG = 2;
102
103 boolean mIsCreating = false;
104
105 final Handler mHandler = new Handler() {
106 @Override
107 public void handleMessage(Message msg) {
108 switch (msg.what) {
109 case KEEP_SCREEN_ON_MSG: {
110 setKeepScreenOn(msg.arg1 != 0);
111 } break;
112 case GET_NEW_SURFACE_MSG: {
113 handleGetNewSurface();
114 } break;
115 }
116 }
117 };
118
119 boolean mRequestedVisible = false;
120 int mRequestedWidth = -1;
121 int mRequestedHeight = -1;
122 int mRequestedFormat = PixelFormat.OPAQUE;
123 int mRequestedType = -1;
124
125 boolean mHaveFrame = false;
126 boolean mDestroyReportNeeded = false;
127 boolean mNewSurfaceNeeded = false;
128 long mLastLockTime = 0;
129
130 boolean mVisible = false;
131 int mLeft = -1;
132 int mTop = -1;
133 int mWidth = -1;
134 int mHeight = -1;
135 int mFormat = -1;
136 int mType = -1;
137 final Rect mSurfaceFrame = new Rect();
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700138 private final float mAppScale;
139 private final float mAppScaleInverted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140
141 public SurfaceView(Context context) {
142 super(context);
143 setWillNotDraw(true);
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700144 mAppScale = context.getApplicationScale();
145 mAppScaleInverted = 1.0f / mAppScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
147
148 public SurfaceView(Context context, AttributeSet attrs) {
149 super(context, attrs);
150 setWillNotDraw(true);
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700151 mAppScale = context.getApplicationScale();
152 mAppScaleInverted = 1.0f / mAppScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 }
154
155 public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
156 super(context, attrs, defStyle);
157 setWillNotDraw(true);
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700158 mAppScale = context.getApplicationScale();
159 mAppScaleInverted = 1.0f / mAppScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 }
161
162 /**
163 * Return the SurfaceHolder providing access and control over this
164 * SurfaceView's underlying surface.
165 *
166 * @return SurfaceHolder The holder of the surface.
167 */
168 public SurfaceHolder getHolder() {
169 return mSurfaceHolder;
170 }
171
172 @Override
173 protected void onAttachedToWindow() {
174 super.onAttachedToWindow();
175 mParent.requestTransparentRegion(this);
176 mSession = getWindowSession();
177 mLayout.token = getWindowToken();
178 mLayout.setTitle("SurfaceView");
179 }
180
181 @Override
182 protected void onWindowVisibilityChanged(int visibility) {
183 super.onWindowVisibilityChanged(visibility);
184 mRequestedVisible = visibility == VISIBLE;
185 updateWindow(false);
186 }
187
188 @Override
189 protected void onDetachedFromWindow() {
190 mRequestedVisible = false;
191 updateWindow(false);
192 mHaveFrame = false;
193 if (mWindow != null) {
194 try {
195 mSession.remove(mWindow);
196 } catch (RemoteException ex) {
197 }
198 mWindow = null;
199 }
200 mSession = null;
201 mLayout.token = null;
202
203 super.onDetachedFromWindow();
204 }
205
206 @Override
207 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
208 int width = getDefaultSize(mRequestedWidth, widthMeasureSpec);
209 int height = getDefaultSize(mRequestedHeight, heightMeasureSpec);
210 setMeasuredDimension(width, height);
211 }
212
213 @Override
214 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
215 super.onScrollChanged(l, t, oldl, oldt);
216 updateWindow(false);
217 }
218
219 @Override
220 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
221 super.onSizeChanged(w, h, oldw, oldh);
222 updateWindow(false);
223 }
224
225 @Override
226 public boolean gatherTransparentRegion(Region region) {
227 boolean opaque = true;
228 if ((mPrivateFlags & SKIP_DRAW) == 0) {
229 // this view draws, remove it from the transparent region
230 opaque = super.gatherTransparentRegion(region);
231 } else if (region != null) {
232 int w = getWidth();
233 int h = getHeight();
234 if (w>0 && h>0) {
235 getLocationInWindow(mLocation);
236 // otherwise, punch a hole in the whole hierarchy
237 int l = mLocation[0];
238 int t = mLocation[1];
239 region.op(l, t, l+w, t+h, Region.Op.UNION);
240 }
241 }
242 if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
243 opaque = false;
244 }
245 return opaque;
246 }
247
248 @Override
249 public void draw(Canvas canvas) {
250 // draw() is not called when SKIP_DRAW is set
251 if ((mPrivateFlags & SKIP_DRAW) == 0) {
252 // punch a whole in the view-hierarchy below us
253 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
254 }
255 super.draw(canvas);
256 }
257
258 @Override
259 protected void dispatchDraw(Canvas canvas) {
260 // if SKIP_DRAW is cleared, draw() has already punched a hole
261 if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
262 // punch a whole in the view-hierarchy below us
263 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
264 }
265 // reposition ourselves where the surface is
266 mHaveFrame = true;
267 updateWindow(false);
268 super.dispatchDraw(canvas);
269 }
270
271 private void updateWindow(boolean force) {
272 if (!mHaveFrame) {
273 return;
274 }
275
276 int myWidth = mRequestedWidth;
277 if (myWidth <= 0) myWidth = getWidth();
278 int myHeight = mRequestedHeight;
279 if (myHeight <= 0) myHeight = getHeight();
280
281 getLocationInWindow(mLocation);
282 final boolean creating = mWindow == null;
283 final boolean formatChanged = mFormat != mRequestedFormat;
284 final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
285 final boolean visibleChanged = mVisible != mRequestedVisible
286 || mNewSurfaceNeeded;
287 final boolean typeChanged = mType != mRequestedType;
288 if (force || creating || formatChanged || sizeChanged || visibleChanged
289 || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
290
291 if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
292 + " format=" + formatChanged + " size=" + sizeChanged
293 + " visible=" + visibleChanged
294 + " left=" + (mLeft != mLocation[0])
295 + " top=" + (mTop != mLocation[1]));
296
297 try {
298 final boolean visible = mVisible = mRequestedVisible;
299 mLeft = mLocation[0];
300 mTop = mLocation[1];
301 mWidth = myWidth;
302 mHeight = myHeight;
303 mFormat = mRequestedFormat;
304 mType = mRequestedType;
305
306 mLayout.x = mLeft;
307 mLayout.y = mTop;
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700308 mLayout.width = (int) (getWidth() * mAppScale);
309 mLayout.height = (int) (getHeight() * mAppScale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 mLayout.format = mRequestedFormat;
311 mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
312 | WindowManager.LayoutParams.FLAG_SCALED
313 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
314 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
315 ;
316
317 mLayout.memoryType = mRequestedType;
318
319 if (mWindow == null) {
320 mWindow = new MyWindow(this);
321 mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
322 mLayout.gravity = Gravity.LEFT|Gravity.TOP;
323 mSession.add(mWindow, mLayout,
324 mVisible ? VISIBLE : GONE, mContentInsets);
325 }
326
327 if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
328 reportSurfaceDestroyed();
329 }
330
331 mNewSurfaceNeeded = false;
332
333 mSurfaceLock.lock();
334 mDrawingStopped = !visible;
335 final int relayoutResult = mSession.relayout(
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700336 mWindow, mLayout, (int) (mWidth * mAppScale), (int) (mHeight * mAppScale),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
338 mVisibleInsets, mSurface);
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700339
340 mContentInsets.scale(mAppScaleInverted);
341 mVisibleInsets.scale(mAppScaleInverted);
342 mWinFrame.scale(mAppScaleInverted);
343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 if (localLOGV) Log.i(TAG, "New surface: " + mSurface
345 + ", vis=" + visible + ", frame=" + mWinFrame);
346 mSurfaceFrame.left = 0;
347 mSurfaceFrame.top = 0;
348 mSurfaceFrame.right = mWinFrame.width();
349 mSurfaceFrame.bottom = mWinFrame.height();
350 mSurfaceLock.unlock();
351
352 try {
353 if (visible) {
354 mDestroyReportNeeded = true;
355
356 SurfaceHolder.Callback callbacks[];
357 synchronized (mCallbacks) {
358 callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
359 mCallbacks.toArray(callbacks);
360 }
361
362 if (visibleChanged) {
363 mIsCreating = true;
364 for (SurfaceHolder.Callback c : callbacks) {
365 c.surfaceCreated(mSurfaceHolder);
366 }
367 }
368 if (creating || formatChanged || sizeChanged
369 || visibleChanged) {
370 for (SurfaceHolder.Callback c : callbacks) {
371 c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
372 }
373 }
374 }
375 } finally {
376 mIsCreating = false;
377 if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
378 mSession.finishDrawing(mWindow);
379 }
380 }
381 } catch (RemoteException ex) {
382 }
383 if (localLOGV) Log.v(
384 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
385 " w=" + mLayout.width + " h=" + mLayout.height +
386 ", frame=" + mSurfaceFrame);
387 }
388 }
389
390 private void reportSurfaceDestroyed() {
391 if (mDestroyReportNeeded) {
392 mDestroyReportNeeded = false;
393 SurfaceHolder.Callback callbacks[];
394 synchronized (mCallbacks) {
395 callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
396 mCallbacks.toArray(callbacks);
397 }
398 for (SurfaceHolder.Callback c : callbacks) {
399 c.surfaceDestroyed(mSurfaceHolder);
400 }
401 }
402 super.onDetachedFromWindow();
403 }
404
405 void handleGetNewSurface() {
406 mNewSurfaceNeeded = true;
407 updateWindow(false);
408 }
409
410 private static class MyWindow extends IWindow.Stub {
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700411 private final WeakReference<SurfaceView> mSurfaceView;
412 private final float mAppScale;
413 private final float mAppScaleInverted;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414
415 public MyWindow(SurfaceView surfaceView) {
416 mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700417 mAppScale = surfaceView.getContext().getApplicationScale();
418 mAppScaleInverted = 1.0f / mAppScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420
421 public void resized(int w, int h, Rect coveredInsets,
422 Rect visibleInsets, boolean reportDraw) {
423 SurfaceView surfaceView = mSurfaceView.get();
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700424 float scale = mAppScaleInverted;
425 w *= scale;
426 h *= scale;
427 coveredInsets.scale(scale);
428 visibleInsets.scale(scale);
429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 if (surfaceView != null) {
431 if (localLOGV) Log.v(
432 "SurfaceView", surfaceView + " got resized: w=" +
433 w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
434 synchronized (this) {
435 if (mCurWidth != w || mCurHeight != h) {
436 mCurWidth = w;
437 mCurHeight = h;
438 }
439 if (reportDraw) {
440 try {
441 surfaceView.mSession.finishDrawing(surfaceView.mWindow);
442 } catch (RemoteException e) {
443 }
444 }
445 }
446 }
447 }
448
449 public void dispatchKey(KeyEvent event) {
450 SurfaceView surfaceView = mSurfaceView.get();
451 if (surfaceView != null) {
452 //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
453 if (surfaceView.mSession != null && surfaceView.mSurface != null) {
454 try {
455 surfaceView.mSession.finishKey(surfaceView.mWindow);
456 } catch (RemoteException ex) {
457 }
458 }
459 }
460 }
461
462 public void dispatchPointer(MotionEvent event, long eventTime) {
463 Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
464 //if (mSession != null && mSurface != null) {
465 // try {
466 // //mSession.finishKey(mWindow);
467 // } catch (RemoteException ex) {
468 // }
469 //}
470 }
471
472 public void dispatchTrackball(MotionEvent event, long eventTime) {
473 Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
474 //if (mSession != null && mSurface != null) {
475 // try {
476 // //mSession.finishKey(mWindow);
477 // } catch (RemoteException ex) {
478 // }
479 //}
480 }
481
482 public void dispatchAppVisibility(boolean visible) {
483 // The point of SurfaceView is to let the app control the surface.
484 }
485
486 public void dispatchGetNewSurface() {
487 SurfaceView surfaceView = mSurfaceView.get();
488 if (surfaceView != null) {
489 Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
490 surfaceView.mHandler.sendMessage(msg);
491 }
492 }
493
494 public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
495 Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
496 }
497
498 public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
499 }
500
501 int mCurWidth = -1;
502 int mCurHeight = -1;
503 }
504
505 private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
506
507 private static final String LOG_TAG = "SurfaceHolder";
508
509 public boolean isCreating() {
510 return mIsCreating;
511 }
512
513 public void addCallback(Callback callback) {
514 synchronized (mCallbacks) {
515 // This is a linear search, but in practice we'll
516 // have only a couple callbacks, so it doesn't matter.
517 if (mCallbacks.contains(callback) == false) {
518 mCallbacks.add(callback);
519 }
520 }
521 }
522
523 public void removeCallback(Callback callback) {
524 synchronized (mCallbacks) {
525 mCallbacks.remove(callback);
526 }
527 }
528
529 public void setFixedSize(int width, int height) {
530 if (mRequestedWidth != width || mRequestedHeight != height) {
531 mRequestedWidth = width;
532 mRequestedHeight = height;
533 requestLayout();
534 }
535 }
536
537 public void setSizeFromLayout() {
538 if (mRequestedWidth != -1 || mRequestedHeight != -1) {
539 mRequestedWidth = mRequestedHeight = -1;
540 requestLayout();
541 }
542 }
543
544 public void setFormat(int format) {
545 mRequestedFormat = format;
546 if (mWindow != null) {
547 updateWindow(false);
548 }
549 }
550
551 public void setType(int type) {
552 switch (type) {
553 case SURFACE_TYPE_NORMAL:
554 case SURFACE_TYPE_HARDWARE:
555 case SURFACE_TYPE_GPU:
556 case SURFACE_TYPE_PUSH_BUFFERS:
557 mRequestedType = type;
558 if (mWindow != null) {
559 updateWindow(false);
560 }
561 break;
562 }
563 }
564
565 public void setKeepScreenOn(boolean screenOn) {
566 Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
567 msg.arg1 = screenOn ? 1 : 0;
568 mHandler.sendMessage(msg);
569 }
570
571 public Canvas lockCanvas() {
572 return internalLockCanvas(null);
573 }
574
575 public Canvas lockCanvas(Rect dirty) {
576 return internalLockCanvas(dirty);
577 }
578
579 private final Canvas internalLockCanvas(Rect dirty) {
580 if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
581 throw new BadSurfaceTypeException(
582 "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
583 }
584 mSurfaceLock.lock();
585
586 if (localLOGV) Log.i(TAG, "Locking canvas... stopped="
587 + mDrawingStopped + ", win=" + mWindow);
588
589 Canvas c = null;
590 if (!mDrawingStopped && mWindow != null) {
591 Rect frame = dirty != null ? dirty : mSurfaceFrame;
Mitsuru Oshima8169dae2009-04-28 18:12:09 -0700592 frame.scale(mAppScale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 try {
594 c = mSurface.lockCanvas(frame);
595 } catch (Exception e) {
596 Log.e(LOG_TAG, "Exception locking surface", e);
597 }
598 }
599
600 if (localLOGV) Log.i(TAG, "Returned canvas: " + c);
601 if (c != null) {
602 mLastLockTime = SystemClock.uptimeMillis();
603 return c;
604 }
605
606 // If the Surface is not ready to be drawn, then return null,
607 // but throttle calls to this function so it isn't called more
608 // than every 100ms.
609 long now = SystemClock.uptimeMillis();
610 long nextTime = mLastLockTime + 100;
611 if (nextTime > now) {
612 try {
613 Thread.sleep(nextTime-now);
614 } catch (InterruptedException e) {
615 }
616 now = SystemClock.uptimeMillis();
617 }
618 mLastLockTime = now;
619 mSurfaceLock.unlock();
620
621 return null;
622 }
623
624 public void unlockCanvasAndPost(Canvas canvas) {
625 mSurface.unlockCanvasAndPost(canvas);
626 mSurfaceLock.unlock();
627 }
628
629 public Surface getSurface() {
630 return mSurface;
631 }
632
633 public Rect getSurfaceFrame() {
634 return mSurfaceFrame;
635 }
636 };
637}