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