1 /*
2  * Copyright (C) 2014 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 
17 package com.android.incallui.videosurface.impl;
18 
19 import android.graphics.Point;
20 import android.graphics.SurfaceTexture;
21 import android.os.Build.VERSION;
22 import android.os.Build.VERSION_CODES;
23 import android.view.Surface;
24 import android.view.TextureView;
25 import android.view.View;
26 import com.android.dialer.common.LogUtil;
27 import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
28 import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
29 import java.util.Locale;
30 import java.util.Objects;
31 
32 /**
33  * Represents a {@link TextureView} and its associated {@link SurfaceTexture} and {@link Surface}.
34  * Used to manage the lifecycle of these objects across device orientation changes.
35  */
36 public class VideoSurfaceTextureImpl implements VideoSurfaceTexture {
37   @SurfaceType private final int surfaceType;
38   private final boolean isPixel2017;
39 
40   private VideoSurfaceDelegate delegate;
41   private TextureView textureView;
42   private Surface savedSurface;
43   private SurfaceTexture savedSurfaceTexture;
44   private Point surfaceDimensions;
45   private Point sourceVideoDimensions;
46   private boolean isDoneWithSurface;
47 
VideoSurfaceTextureImpl(boolean isPixel2017, @SurfaceType int surfaceType)48   public VideoSurfaceTextureImpl(boolean isPixel2017, @SurfaceType int surfaceType) {
49     this.isPixel2017 = isPixel2017;
50     this.surfaceType = surfaceType;
51   }
52 
53   @Override
setDelegate(VideoSurfaceDelegate delegate)54   public void setDelegate(VideoSurfaceDelegate delegate) {
55     LogUtil.i("VideoSurfaceTextureImpl.setDelegate", "delegate: " + delegate + " " + toString());
56     this.delegate = delegate;
57   }
58 
59   @Override
getSurfaceType()60   public int getSurfaceType() {
61     return surfaceType;
62   }
63 
64   @Override
getSavedSurface()65   public Surface getSavedSurface() {
66     return savedSurface;
67   }
68 
69   @Override
setSurfaceDimensions(Point surfaceDimensions)70   public void setSurfaceDimensions(Point surfaceDimensions) {
71     LogUtil.i(
72         "VideoSurfaceTextureImpl.setSurfaceDimensions",
73         "surfaceDimensions: " + surfaceDimensions + " " + toString());
74     this.surfaceDimensions = surfaceDimensions;
75     if (surfaceDimensions != null && savedSurfaceTexture != null) {
76       // Only do this on O (not at least O) because we expect this issue to be fixed in OMR1
77       if (VERSION.SDK_INT == VERSION_CODES.O && isPixel2017) {
78         LogUtil.i(
79             "VideoSurfaceTextureImpl.setSurfaceDimensions",
80             "skip setting default buffer size on Pixel 2017 ODR");
81         return;
82       }
83       savedSurfaceTexture.setDefaultBufferSize(surfaceDimensions.x, surfaceDimensions.y);
84     }
85   }
86 
87   @Override
getSurfaceDimensions()88   public Point getSurfaceDimensions() {
89     return surfaceDimensions;
90   }
91 
92   @Override
setSourceVideoDimensions(Point sourceVideoDimensions)93   public void setSourceVideoDimensions(Point sourceVideoDimensions) {
94     this.sourceVideoDimensions = sourceVideoDimensions;
95   }
96 
97   @Override
getSourceVideoDimensions()98   public Point getSourceVideoDimensions() {
99     return sourceVideoDimensions;
100   }
101 
102   @Override
attachToTextureView(TextureView textureView)103   public void attachToTextureView(TextureView textureView) {
104     if (this.textureView == textureView) {
105       return;
106     }
107     LogUtil.i("VideoSurfaceTextureImpl.attachToTextureView", toString());
108 
109     if (this.textureView != null) {
110       this.textureView.setOnClickListener(null);
111       this.textureView.setSurfaceTextureListener(null);
112     }
113 
114     this.textureView = textureView;
115     textureView.setSurfaceTextureListener(new SurfaceTextureListener());
116     textureView.setOnClickListener(new OnClickListener());
117 
118     boolean areSameSurfaces = Objects.equals(savedSurfaceTexture, textureView.getSurfaceTexture());
119     LogUtil.i("VideoSurfaceTextureImpl.attachToTextureView", "areSameSurfaces: " + areSameSurfaces);
120     if (savedSurfaceTexture != null && !areSameSurfaces) {
121       textureView.setSurfaceTexture(savedSurfaceTexture);
122       if (surfaceDimensions != null && createSurface(surfaceDimensions.x, surfaceDimensions.y)) {
123         onSurfaceCreated();
124       }
125     }
126     isDoneWithSurface = false;
127   }
128 
129   @Override
setDoneWithSurface()130   public void setDoneWithSurface() {
131     LogUtil.i("VideoSurfaceTextureImpl.setDoneWithSurface", toString());
132     isDoneWithSurface = true;
133     if (textureView != null && textureView.isAvailable()) {
134       return;
135     }
136     if (savedSurface != null) {
137       onSurfaceReleased();
138       savedSurface.release();
139       savedSurface = null;
140     }
141     if (savedSurfaceTexture != null) {
142       savedSurfaceTexture.release();
143       savedSurfaceTexture = null;
144     }
145   }
146 
createSurface(int width, int height)147   private boolean createSurface(int width, int height) {
148     LogUtil.i(
149         "VideoSurfaceTextureImpl.createSurface",
150         "width: " + width + ", height: " + height + " " + toString());
151     savedSurfaceTexture.setDefaultBufferSize(width, height);
152     if (savedSurface != null) {
153       savedSurface.release();
154     }
155     savedSurface = new Surface(savedSurfaceTexture);
156     return true;
157   }
158 
onSurfaceCreated()159   private void onSurfaceCreated() {
160     if (delegate != null) {
161       delegate.onSurfaceCreated(this);
162     } else {
163       LogUtil.e("VideoSurfaceTextureImpl.onSurfaceCreated", "delegate is null. " + toString());
164     }
165   }
166 
onSurfaceReleased()167   private void onSurfaceReleased() {
168     if (delegate != null) {
169       delegate.onSurfaceReleased(this);
170     } else {
171       LogUtil.e("VideoSurfaceTextureImpl.onSurfaceReleased", "delegate is null. " + toString());
172     }
173   }
174 
175   @Override
toString()176   public String toString() {
177     return String.format(
178         Locale.US,
179         "VideoSurfaceTextureImpl<%s%s%s%s>",
180         (surfaceType == SURFACE_TYPE_LOCAL ? "local, " : "remote, "),
181         (savedSurface == null ? "no-surface, " : ""),
182         (savedSurfaceTexture == null ? "no-texture, " : ""),
183         (surfaceDimensions == null
184             ? "(-1 x -1)"
185             : (surfaceDimensions.x + " x " + surfaceDimensions.y)));
186   }
187 
188   private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
189     @Override
onSurfaceTextureAvailable(SurfaceTexture newSurfaceTexture, int width, int height)190     public void onSurfaceTextureAvailable(SurfaceTexture newSurfaceTexture, int width, int height) {
191       LogUtil.i(
192           "SurfaceTextureListener.onSurfaceTextureAvailable",
193           "newSurfaceTexture: "
194               + newSurfaceTexture
195               + " "
196               + VideoSurfaceTextureImpl.this.toString());
197 
198       // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
199       // If a saved {@link SurfaceTexture} is available, we are re-creating after an
200       // orientation change.
201       boolean surfaceCreated;
202       if (savedSurfaceTexture == null) {
203         savedSurfaceTexture = newSurfaceTexture;
204         surfaceCreated = createSurface(width, height);
205       } else {
206         // A saved SurfaceTexture was found.
207         LogUtil.i(
208             "SurfaceTextureListener.onSurfaceTextureAvailable", "replacing with cached surface...");
209         textureView.setSurfaceTexture(savedSurfaceTexture);
210         surfaceCreated = true;
211       }
212 
213       // Inform the delegate that the surface is available.
214       if (surfaceCreated) {
215         onSurfaceCreated();
216       }
217     }
218 
219     @Override
onSurfaceTextureDestroyed(SurfaceTexture destroyedSurfaceTexture)220     public boolean onSurfaceTextureDestroyed(SurfaceTexture destroyedSurfaceTexture) {
221       LogUtil.i(
222           "SurfaceTextureListener.onSurfaceTextureDestroyed",
223           "destroyedSurfaceTexture: %s, %s, isDoneWithSurface: %b",
224           destroyedSurfaceTexture,
225           VideoSurfaceTextureImpl.this.toString(),
226           isDoneWithSurface);
227       if (delegate != null) {
228         delegate.onSurfaceDestroyed(VideoSurfaceTextureImpl.this);
229       } else {
230         LogUtil.e("SurfaceTextureListener.onSurfaceTextureDestroyed", "delegate is null");
231       }
232 
233       if (isDoneWithSurface) {
234         onSurfaceReleased();
235         if (savedSurface != null) {
236           savedSurface.release();
237           savedSurface = null;
238         }
239       }
240       return isDoneWithSurface;
241     }
242 
243     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)244     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
245 
246     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)247     public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
248   }
249 
250   private class OnClickListener implements View.OnClickListener {
251     @Override
onClick(View view)252     public void onClick(View view) {
253       if (delegate != null) {
254         delegate.onSurfaceClick(VideoSurfaceTextureImpl.this);
255       } else {
256         LogUtil.e("OnClickListener.onClick", "delegate is null");
257       }
258     }
259   }
260 }
261