blob: e928998b6d00a83f165bfbb7c002ede92e240625 [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();
138
139 public SurfaceView(Context context) {
140 super(context);
141 setWillNotDraw(true);
142 }
143
144 public SurfaceView(Context context, AttributeSet attrs) {
145 super(context, attrs);
146 setWillNotDraw(true);
147 }
148
149 public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
150 super(context, attrs, defStyle);
151 setWillNotDraw(true);
152 }
153
154 /**
155 * Return the SurfaceHolder providing access and control over this
156 * SurfaceView's underlying surface.
157 *
158 * @return SurfaceHolder The holder of the surface.
159 */
160 public SurfaceHolder getHolder() {
161 return mSurfaceHolder;
162 }
163
164 @Override
165 protected void onAttachedToWindow() {
166 super.onAttachedToWindow();
167 mParent.requestTransparentRegion(this);
168 mSession = getWindowSession();
169 mLayout.token = getWindowToken();
170 mLayout.setTitle("SurfaceView");
171 }
172
173 @Override
174 protected void onWindowVisibilityChanged(int visibility) {
175 super.onWindowVisibilityChanged(visibility);
176 mRequestedVisible = visibility == VISIBLE;
177 updateWindow(false);
178 }
179
180 @Override
181 protected void onDetachedFromWindow() {
182 mRequestedVisible = false;
183 updateWindow(false);
184 mHaveFrame = false;
185 if (mWindow != null) {
186 try {
187 mSession.remove(mWindow);
188 } catch (RemoteException ex) {
189 }
190 mWindow = null;
191 }
192 mSession = null;
193 mLayout.token = null;
194
195 super.onDetachedFromWindow();
196 }
197
198 @Override
199 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
200 int width = getDefaultSize(mRequestedWidth, widthMeasureSpec);
201 int height = getDefaultSize(mRequestedHeight, heightMeasureSpec);
202 setMeasuredDimension(width, height);
203 }
204
205 @Override
206 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
207 super.onScrollChanged(l, t, oldl, oldt);
208 updateWindow(false);
209 }
210
211 @Override
212 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
213 super.onSizeChanged(w, h, oldw, oldh);
214 updateWindow(false);
215 }
216
217 @Override
218 public boolean gatherTransparentRegion(Region region) {
219 boolean opaque = true;
220 if ((mPrivateFlags & SKIP_DRAW) == 0) {
221 // this view draws, remove it from the transparent region
222 opaque = super.gatherTransparentRegion(region);
223 } else if (region != null) {
224 int w = getWidth();
225 int h = getHeight();
226 if (w>0 && h>0) {
227 getLocationInWindow(mLocation);
228 // otherwise, punch a hole in the whole hierarchy
229 int l = mLocation[0];
230 int t = mLocation[1];
231 region.op(l, t, l+w, t+h, Region.Op.UNION);
232 }
233 }
234 if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
235 opaque = false;
236 }
237 return opaque;
238 }
239
240 @Override
241 public void draw(Canvas canvas) {
242 // draw() is not called when SKIP_DRAW is set
243 if ((mPrivateFlags & SKIP_DRAW) == 0) {
244 // punch a whole in the view-hierarchy below us
245 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
246 }
247 super.draw(canvas);
248 }
249
250 @Override
251 protected void dispatchDraw(Canvas canvas) {
252 // if SKIP_DRAW is cleared, draw() has already punched a hole
253 if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
254 // punch a whole in the view-hierarchy below us
255 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
256 }
257 // reposition ourselves where the surface is
258 mHaveFrame = true;
259 updateWindow(false);
260 super.dispatchDraw(canvas);
261 }
262
263 private void updateWindow(boolean force) {
264 if (!mHaveFrame) {
265 return;
266 }
267
268 int myWidth = mRequestedWidth;
269 if (myWidth <= 0) myWidth = getWidth();
270 int myHeight = mRequestedHeight;
271 if (myHeight <= 0) myHeight = getHeight();
272
273 getLocationInWindow(mLocation);
274 final boolean creating = mWindow == null;
275 final boolean formatChanged = mFormat != mRequestedFormat;
276 final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
277 final boolean visibleChanged = mVisible != mRequestedVisible
278 || mNewSurfaceNeeded;
279 final boolean typeChanged = mType != mRequestedType;
280 if (force || creating || formatChanged || sizeChanged || visibleChanged
281 || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
282
283 if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
284 + " format=" + formatChanged + " size=" + sizeChanged
285 + " visible=" + visibleChanged
286 + " left=" + (mLeft != mLocation[0])
287 + " top=" + (mTop != mLocation[1]));
288
289 try {
290 final boolean visible = mVisible = mRequestedVisible;
291 mLeft = mLocation[0];
292 mTop = mLocation[1];
293 mWidth = myWidth;
294 mHeight = myHeight;
295 mFormat = mRequestedFormat;
296 mType = mRequestedType;
297
298 mLayout.x = mLeft;
299 mLayout.y = mTop;
300 mLayout.width = getWidth();
301 mLayout.height = getHeight();
302 mLayout.format = mRequestedFormat;
303 mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
304 | WindowManager.LayoutParams.FLAG_SCALED
305 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
306 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
307 ;
308
309 mLayout.memoryType = mRequestedType;
310
311 if (mWindow == null) {
312 mWindow = new MyWindow(this);
313 mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
314 mLayout.gravity = Gravity.LEFT|Gravity.TOP;
315 mSession.add(mWindow, mLayout,
316 mVisible ? VISIBLE : GONE, mContentInsets);
317 }
318
319 if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
320 reportSurfaceDestroyed();
321 }
322
323 mNewSurfaceNeeded = false;
324
325 mSurfaceLock.lock();
326 mDrawingStopped = !visible;
327 final int relayoutResult = mSession.relayout(
328 mWindow, mLayout, mWidth, mHeight,
329 visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
330 mVisibleInsets, mSurface);
331 if (localLOGV) Log.i(TAG, "New surface: " + mSurface
332 + ", vis=" + visible + ", frame=" + mWinFrame);
333 mSurfaceFrame.left = 0;
334 mSurfaceFrame.top = 0;
335 mSurfaceFrame.right = mWinFrame.width();
336 mSurfaceFrame.bottom = mWinFrame.height();
337 mSurfaceLock.unlock();
338
339 try {
340 if (visible) {
341 mDestroyReportNeeded = true;
342
343 SurfaceHolder.Callback callbacks[];
344 synchronized (mCallbacks) {
345 callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
346 mCallbacks.toArray(callbacks);
347 }
348
349 if (visibleChanged) {
350 mIsCreating = true;
351 for (SurfaceHolder.Callback c : callbacks) {
352 c.surfaceCreated(mSurfaceHolder);
353 }
354 }
355 if (creating || formatChanged || sizeChanged
356 || visibleChanged) {
357 for (SurfaceHolder.Callback c : callbacks) {
358 c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
359 }
360 }
361 }
362 } finally {
363 mIsCreating = false;
364 if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
365 mSession.finishDrawing(mWindow);
366 }
367 }
368 } catch (RemoteException ex) {
369 }
370 if (localLOGV) Log.v(
371 TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
372 " w=" + mLayout.width + " h=" + mLayout.height +
373 ", frame=" + mSurfaceFrame);
374 }
375 }
376
377 private void reportSurfaceDestroyed() {
378 if (mDestroyReportNeeded) {
379 mDestroyReportNeeded = false;
380 SurfaceHolder.Callback callbacks[];
381 synchronized (mCallbacks) {
382 callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
383 mCallbacks.toArray(callbacks);
384 }
385 for (SurfaceHolder.Callback c : callbacks) {
386 c.surfaceDestroyed(mSurfaceHolder);
387 }
388 }
389 super.onDetachedFromWindow();
390 }
391
392 void handleGetNewSurface() {
393 mNewSurfaceNeeded = true;
394 updateWindow(false);
395 }
396
397 private static class MyWindow extends IWindow.Stub {
398 private WeakReference<SurfaceView> mSurfaceView;
399
400 public MyWindow(SurfaceView surfaceView) {
401 mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
402 }
403
404 public void resized(int w, int h, Rect coveredInsets,
405 Rect visibleInsets, boolean reportDraw) {
406 SurfaceView surfaceView = mSurfaceView.get();
407 if (surfaceView != null) {
408 if (localLOGV) Log.v(
409 "SurfaceView", surfaceView + " got resized: w=" +
410 w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
411 synchronized (this) {
412 if (mCurWidth != w || mCurHeight != h) {
413 mCurWidth = w;
414 mCurHeight = h;
415 }
416 if (reportDraw) {
417 try {
418 surfaceView.mSession.finishDrawing(surfaceView.mWindow);
419 } catch (RemoteException e) {
420 }
421 }
422 }
423 }
424 }
425
426 public void dispatchKey(KeyEvent event) {
427 SurfaceView surfaceView = mSurfaceView.get();
428 if (surfaceView != null) {
429 //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
430 if (surfaceView.mSession != null && surfaceView.mSurface != null) {
431 try {
432 surfaceView.mSession.finishKey(surfaceView.mWindow);
433 } catch (RemoteException ex) {
434 }
435 }
436 }
437 }
438
439 public void dispatchPointer(MotionEvent event, long eventTime) {
440 Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
441 //if (mSession != null && mSurface != null) {
442 // try {
443 // //mSession.finishKey(mWindow);
444 // } catch (RemoteException ex) {
445 // }
446 //}
447 }
448
449 public void dispatchTrackball(MotionEvent event, long eventTime) {
450 Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
451 //if (mSession != null && mSurface != null) {
452 // try {
453 // //mSession.finishKey(mWindow);
454 // } catch (RemoteException ex) {
455 // }
456 //}
457 }
458
459 public void dispatchAppVisibility(boolean visible) {
460 // The point of SurfaceView is to let the app control the surface.
461 }
462
463 public void dispatchGetNewSurface() {
464 SurfaceView surfaceView = mSurfaceView.get();
465 if (surfaceView != null) {
466 Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
467 surfaceView.mHandler.sendMessage(msg);
468 }
469 }
470
471 public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
472 Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
473 }
474
475 public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
476 }
477
478 int mCurWidth = -1;
479 int mCurHeight = -1;
480 }
481
482 private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
483
484 private static final String LOG_TAG = "SurfaceHolder";
485
486 public boolean isCreating() {
487 return mIsCreating;
488 }
489
490 public void addCallback(Callback callback) {
491 synchronized (mCallbacks) {
492 // This is a linear search, but in practice we'll
493 // have only a couple callbacks, so it doesn't matter.
494 if (mCallbacks.contains(callback) == false) {
495 mCallbacks.add(callback);
496 }
497 }
498 }
499
500 public void removeCallback(Callback callback) {
501 synchronized (mCallbacks) {
502 mCallbacks.remove(callback);
503 }
504 }
505
506 public void setFixedSize(int width, int height) {
507 if (mRequestedWidth != width || mRequestedHeight != height) {
508 mRequestedWidth = width;
509 mRequestedHeight = height;
510 requestLayout();
511 }
512 }
513
514 public void setSizeFromLayout() {
515 if (mRequestedWidth != -1 || mRequestedHeight != -1) {
516 mRequestedWidth = mRequestedHeight = -1;
517 requestLayout();
518 }
519 }
520
521 public void setFormat(int format) {
522 mRequestedFormat = format;
523 if (mWindow != null) {
524 updateWindow(false);
525 }
526 }
527
528 public void setType(int type) {
529 switch (type) {
530 case SURFACE_TYPE_NORMAL:
531 case SURFACE_TYPE_HARDWARE:
532 case SURFACE_TYPE_GPU:
533 case SURFACE_TYPE_PUSH_BUFFERS:
534 mRequestedType = type;
535 if (mWindow != null) {
536 updateWindow(false);
537 }
538 break;
539 }
540 }
541
542 public void setKeepScreenOn(boolean screenOn) {
543 Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG);
544 msg.arg1 = screenOn ? 1 : 0;
545 mHandler.sendMessage(msg);
546 }
547
548 public Canvas lockCanvas() {
549 return internalLockCanvas(null);
550 }
551
552 public Canvas lockCanvas(Rect dirty) {
553 return internalLockCanvas(dirty);
554 }
555
556 private final Canvas internalLockCanvas(Rect dirty) {
557 if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
558 throw new BadSurfaceTypeException(
559 "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
560 }
561 mSurfaceLock.lock();
562
563 if (localLOGV) Log.i(TAG, "Locking canvas... stopped="
564 + mDrawingStopped + ", win=" + mWindow);
565
566 Canvas c = null;
567 if (!mDrawingStopped && mWindow != null) {
568 Rect frame = dirty != null ? dirty : mSurfaceFrame;
569 try {
570 c = mSurface.lockCanvas(frame);
571 } catch (Exception e) {
572 Log.e(LOG_TAG, "Exception locking surface", e);
573 }
574 }
575
576 if (localLOGV) Log.i(TAG, "Returned canvas: " + c);
577 if (c != null) {
578 mLastLockTime = SystemClock.uptimeMillis();
579 return c;
580 }
581
582 // If the Surface is not ready to be drawn, then return null,
583 // but throttle calls to this function so it isn't called more
584 // than every 100ms.
585 long now = SystemClock.uptimeMillis();
586 long nextTime = mLastLockTime + 100;
587 if (nextTime > now) {
588 try {
589 Thread.sleep(nextTime-now);
590 } catch (InterruptedException e) {
591 }
592 now = SystemClock.uptimeMillis();
593 }
594 mLastLockTime = now;
595 mSurfaceLock.unlock();
596
597 return null;
598 }
599
600 public void unlockCanvasAndPost(Canvas canvas) {
601 mSurface.unlockCanvasAndPost(canvas);
602 mSurfaceLock.unlock();
603 }
604
605 public Surface getSurface() {
606 return mSurface;
607 }
608
609 public Rect getSurfaceFrame() {
610 return mSurfaceFrame;
611 }
612 };
613}
614