1 /*
2  * Copyright (C) 2016 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.dialer.callcomposer.camera;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.hardware.Camera;
22 import android.view.View;
23 import android.view.View.MeasureSpec;
24 import android.view.View.OnTouchListener;
25 import com.android.dialer.common.Assert;
26 import com.android.dialer.util.PermissionsUtil;
27 import java.io.IOException;
28 
29 /**
30  * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance
31  * because those classes must inherit from separate Views, so those classes delegate calls to this
32  * helper class. Specifics for each implementation are in CameraPreviewHost
33  */
34 public class CameraPreview {
35   /** Implemented by the camera for rendering. */
36   public interface CameraPreviewHost {
getView()37     View getView();
38 
isValid()39     boolean isValid();
40 
startPreview(final Camera camera)41     void startPreview(final Camera camera) throws IOException;
42 
onCameraPermissionGranted()43     void onCameraPermissionGranted();
44 
setShown()45     void setShown();
46   }
47 
48   private int mCameraWidth = -1;
49   private int mCameraHeight = -1;
50   private boolean mTabHasBeenShown = false;
51   private OnTouchListener mListener;
52 
53   private final CameraPreviewHost mHost;
54 
CameraPreview(final CameraPreviewHost host)55   public CameraPreview(final CameraPreviewHost host) {
56     Assert.isNotNull(host);
57     Assert.isNotNull(host.getView());
58     mHost = host;
59   }
60 
61   // This is set when the tab is actually selected.
setShown()62   public void setShown() {
63     mTabHasBeenShown = true;
64     maybeOpenCamera();
65   }
66 
67   // Opening camera is very expensive. Most of the ANR reports seem to be related to the camera.
68   // So we delay until the camera is actually needed.  See b/23287938
maybeOpenCamera()69   private void maybeOpenCamera() {
70     boolean visible = mHost.getView().getVisibility() == View.VISIBLE;
71     if (mTabHasBeenShown && visible && PermissionsUtil.hasCameraPermissions(getContext())) {
72       CameraManager.get().openCamera();
73     }
74   }
75 
setSize(final Camera.Size size, final int orientation)76   public void setSize(final Camera.Size size, final int orientation) {
77     switch (orientation) {
78       case 0:
79       case 180:
80         mCameraWidth = size.width;
81         mCameraHeight = size.height;
82         break;
83       case 90:
84       case 270:
85       default:
86         mCameraWidth = size.height;
87         mCameraHeight = size.width;
88     }
89     mHost.getView().requestLayout();
90   }
91 
getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec)92   public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
93     if (mCameraHeight >= 0) {
94       final int width = View.MeasureSpec.getSize(widthMeasureSpec);
95       return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
96     } else {
97       return widthMeasureSpec;
98     }
99   }
100 
getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec)101   public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
102     if (mCameraHeight >= 0) {
103       final int orientation = getContext().getResources().getConfiguration().orientation;
104       final int width = View.MeasureSpec.getSize(widthMeasureSpec);
105       final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight;
106       int height;
107       if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
108         height = (int) (width * aspectRatio);
109       } else {
110         height = (int) (width / aspectRatio);
111       }
112       return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
113     } else {
114       return heightMeasureSpec;
115     }
116   }
117 
118   // onVisibilityChanged is set to Visible when the tab is _created_,
119   //   which may be when the user is viewing a different tab.
onVisibilityChanged(final int visibility)120   public void onVisibilityChanged(final int visibility) {
121     if (PermissionsUtil.hasCameraPermissions(getContext())) {
122       if (visibility == View.VISIBLE) {
123         maybeOpenCamera();
124       } else {
125         CameraManager.get().closeCamera();
126       }
127     }
128   }
129 
getContext()130   public Context getContext() {
131     return mHost.getView().getContext();
132   }
133 
setOnTouchListener(final View.OnTouchListener listener)134   public void setOnTouchListener(final View.OnTouchListener listener) {
135     mListener = listener;
136     mHost.getView().setOnTouchListener(listener);
137   }
138 
setFocusable(boolean focusable)139   public void setFocusable(boolean focusable) {
140     mHost.getView().setOnTouchListener(focusable ? mListener : null);
141   }
142 
getHeight()143   public int getHeight() {
144     return mHost.getView().getHeight();
145   }
146 
onAttachedToWindow()147   public void onAttachedToWindow() {
148     maybeOpenCamera();
149   }
150 
onDetachedFromWindow()151   public void onDetachedFromWindow() {
152     CameraManager.get().closeCamera();
153   }
154 
onRestoreInstanceState()155   public void onRestoreInstanceState() {
156     maybeOpenCamera();
157   }
158 
onCameraPermissionGranted()159   public void onCameraPermissionGranted() {
160     maybeOpenCamera();
161   }
162 
163   /** @return True if the view is valid and prepared for the camera to start showing the preview */
isValid()164   public boolean isValid() {
165     return mHost.isValid();
166   }
167 
168   /**
169    * Starts the camera preview on the current surface. Abstracts out the differences in API from the
170    * CameraManager
171    *
172    * @throws IOException Which is caught by the CameraManager to display an error
173    */
startPreview(final Camera camera)174   public void startPreview(final Camera camera) throws IOException {
175     mHost.startPreview(camera);
176   }
177 }
178