1/*
2 * Copyright (C) 2013 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 com.android.camera;
18
19import android.app.AlertDialog;
20import android.app.ProgressDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.res.Configuration;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Matrix;
28import android.graphics.Point;
29import android.graphics.Rect;
30import android.graphics.SurfaceTexture;
31import android.graphics.drawable.BitmapDrawable;
32import android.graphics.drawable.Drawable;
33import android.util.Log;
34import android.view.LayoutInflater;
35import android.view.TextureView;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.FrameLayout;
39import android.widget.ImageView;
40import android.widget.LinearLayout;
41import android.widget.TextView;
42
43import com.android.camera.ui.CameraControls;
44import com.android.camera.ui.CameraRootView;
45import com.android.camera.ui.ModuleSwitcher;
46import com.android.camera.util.CameraUtil;
47import com.android.camera2.R;
48
49/**
50 * The UI of {@link WideAnglePanoramaModule}.
51 */
52public class WideAnglePanoramaUI implements
53        TextureView.SurfaceTextureListener,
54        ShutterButton.OnShutterButtonListener,
55        CameraRootView.MyDisplayListener,
56        View.OnLayoutChangeListener {
57
58    @SuppressWarnings("unused")
59    private static final String TAG = "CAM_WidePanoramaUI";
60
61    private CameraActivity mActivity;
62    private WideAnglePanoramaController mController;
63
64    private ViewGroup mRootView;
65    private ModuleSwitcher mSwitcher;
66    private FrameLayout mCaptureLayout;
67    private View mReviewLayout;
68    private ImageView mReview;
69    private View mPreviewBorder;
70    private View mLeftIndicator;
71    private View mRightIndicator;
72    private View mCaptureIndicator;
73    private PanoProgressBar mCaptureProgressBar;
74    private PanoProgressBar mSavingProgressBar;
75    private TextView mTooFastPrompt;
76    private View mPreviewLayout;
77    private ViewGroup mReviewControl;
78    private TextureView mTextureView;
79    private ShutterButton mShutterButton;
80    private CameraControls mCameraControls;
81
82    private Matrix mProgressDirectionMatrix = new Matrix();
83    private float[] mProgressAngle = new float[2];
84
85    private DialogHelper mDialogHelper;
86
87    // Color definitions.
88    private int mIndicatorColor;
89    private int mIndicatorColorFast;
90    private int mReviewBackground;
91    private SurfaceTexture mSurfaceTexture;
92    private View mPreviewCover;
93
94    /** Constructor. */
95    public WideAnglePanoramaUI(
96            CameraActivity activity,
97            WideAnglePanoramaController controller,
98            ViewGroup root) {
99        mActivity = activity;
100        mController = controller;
101        mRootView = root;
102
103        createContentView();
104        mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
105        mSwitcher.setCurrentIndex(ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX);
106        mSwitcher.setSwitchListener(mActivity);
107    }
108
109    public void onStartCapture() {
110        hideSwitcher();
111        mShutterButton.setImageResource(R.drawable.btn_shutter_recording);
112        mCaptureIndicator.setVisibility(View.VISIBLE);
113        showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
114    }
115
116    public void showPreviewUI() {
117        mCaptureLayout.setVisibility(View.VISIBLE);
118        showUI();
119    }
120
121    public void onStopCapture() {
122        mCaptureIndicator.setVisibility(View.INVISIBLE);
123        hideTooFastIndication();
124        hideDirectionIndicators();
125    }
126
127    public void hideSwitcher() {
128        mSwitcher.closePopup();
129        mSwitcher.setVisibility(View.INVISIBLE);
130    }
131
132    public void hideUI() {
133        hideSwitcher();
134        mCameraControls.setVisibility(View.INVISIBLE);
135    }
136
137    public void showUI() {
138        showSwitcher();
139        mCameraControls.setVisibility(View.VISIBLE);
140    }
141
142    public void onPreviewFocusChanged(boolean previewFocused) {
143        if (previewFocused) {
144            showUI();
145        } else {
146            hideUI();
147        }
148    }
149
150    public boolean arePreviewControlsVisible() {
151        return (mCameraControls.getVisibility() == View.VISIBLE);
152    }
153
154    public void showSwitcher() {
155        mSwitcher.setVisibility(View.VISIBLE);
156    }
157
158    public void setCaptureProgressOnDirectionChangeListener(
159            PanoProgressBar.OnDirectionChangeListener listener) {
160        mCaptureProgressBar.setOnDirectionChangeListener(listener);
161    }
162
163    public void resetCaptureProgress() {
164        mCaptureProgressBar.reset();
165    }
166
167    public void setMaxCaptureProgress(int max) {
168        mCaptureProgressBar.setMaxProgress(max);
169    }
170
171    public void showCaptureProgress() {
172        mCaptureProgressBar.setVisibility(View.VISIBLE);
173    }
174
175    public void updateCaptureProgress(
176            float panningRateXInDegree, float panningRateYInDegree,
177            float progressHorizontalAngle, float progressVerticalAngle,
178            float maxPanningSpeed) {
179
180        if ((Math.abs(panningRateXInDegree) > maxPanningSpeed)
181                || (Math.abs(panningRateYInDegree) > maxPanningSpeed)) {
182            showTooFastIndication();
183        } else {
184            hideTooFastIndication();
185        }
186
187        // progressHorizontalAngle and progressVerticalAngle are relative to the
188        // camera. Convert them to UI direction.
189        mProgressAngle[0] = progressHorizontalAngle;
190        mProgressAngle[1] = progressVerticalAngle;
191        mProgressDirectionMatrix.mapPoints(mProgressAngle);
192
193        int angleInMajorDirection =
194                (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))
195                        ? (int) mProgressAngle[0]
196                        : (int) mProgressAngle[1];
197        mCaptureProgressBar.setProgress((angleInMajorDirection));
198    }
199
200    public void setProgressOrientation(int orientation) {
201        mProgressDirectionMatrix.reset();
202        mProgressDirectionMatrix.postRotate(orientation);
203    }
204
205    public void showDirectionIndicators(int direction) {
206        switch (direction) {
207            case PanoProgressBar.DIRECTION_NONE:
208                mLeftIndicator.setVisibility(View.VISIBLE);
209                mRightIndicator.setVisibility(View.VISIBLE);
210                break;
211            case PanoProgressBar.DIRECTION_LEFT:
212                mLeftIndicator.setVisibility(View.VISIBLE);
213                mRightIndicator.setVisibility(View.INVISIBLE);
214                break;
215            case PanoProgressBar.DIRECTION_RIGHT:
216                mLeftIndicator.setVisibility(View.INVISIBLE);
217                mRightIndicator.setVisibility(View.VISIBLE);
218                break;
219        }
220    }
221
222    public SurfaceTexture getSurfaceTexture() {
223        return mSurfaceTexture;
224    }
225
226    @Override
227    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
228        mSurfaceTexture = surfaceTexture;
229        mController.onPreviewUIReady();
230    }
231
232    @Override
233    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {
234
235    }
236
237    @Override
238    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
239        mController.onPreviewUIDestroyed();
240        mSurfaceTexture = null;
241        Log.d(TAG, "surfaceTexture is destroyed");
242        return true;
243    }
244
245    @Override
246    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
247        // Make sure preview cover is hidden if preview data is available.
248        if (mPreviewCover.getVisibility() != View.GONE) {
249            mPreviewCover.setVisibility(View.GONE);
250        }
251    }
252
253    private void hideDirectionIndicators() {
254        mLeftIndicator.setVisibility(View.INVISIBLE);
255        mRightIndicator.setVisibility(View.INVISIBLE);
256    }
257
258    public Point getPreviewAreaSize() {
259        return new Point(
260                mTextureView.getWidth(), mTextureView.getHeight());
261    }
262
263    public void reset() {
264        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
265        mReviewLayout.setVisibility(View.GONE);
266        mCaptureProgressBar.setVisibility(View.INVISIBLE);
267    }
268
269    public void showFinalMosaic(Bitmap bitmap, int orientation) {
270        if (bitmap != null && orientation != 0) {
271            Matrix rotateMatrix = new Matrix();
272            rotateMatrix.setRotate(orientation);
273            bitmap = Bitmap.createBitmap(
274                    bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
275                    rotateMatrix, false);
276        }
277
278        mReview.setImageBitmap(bitmap);
279        mCaptureLayout.setVisibility(View.GONE);
280        mReviewLayout.setVisibility(View.VISIBLE);
281    }
282
283    public void onConfigurationChanged(
284            Configuration newConfig, boolean threadRunning) {
285        Drawable lowResReview = null;
286        if (threadRunning) lowResReview = mReview.getDrawable();
287
288        // Change layout in response to configuration change
289        LayoutInflater inflater = (LayoutInflater)
290                mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
291
292        mReviewControl.removeAllViews();
293        inflater.inflate(R.layout.pano_review_control, mReviewControl, true);
294
295        mRootView.bringChildToFront(mCameraControls);
296        setViews(mActivity.getResources());
297        if (threadRunning) {
298            mReview.setImageDrawable(lowResReview);
299            mCaptureLayout.setVisibility(View.GONE);
300            mReviewLayout.setVisibility(View.VISIBLE);
301        }
302    }
303
304    public void resetSavingProgress() {
305        mSavingProgressBar.reset();
306        mSavingProgressBar.setRightIncreasing(true);
307    }
308
309    public void updateSavingProgress(int progress) {
310        mSavingProgressBar.setProgress(progress);
311    }
312
313    @Override
314    public void onShutterButtonFocus(boolean pressed) {
315        // Do nothing.
316    }
317
318    @Override
319    public void onShutterButtonClick() {
320        mController.onShutterButtonClick();
321    }
322
323    @Override
324    public void onLayoutChange(
325            View v, int l, int t, int r, int b,
326            int oldl, int oldt, int oldr, int oldb) {
327        mController.onPreviewUILayoutChange(l, t, r, b);
328    }
329
330    public void showAlertDialog(
331            String title, String failedString,
332            String OKString, Runnable runnable) {
333        mDialogHelper.showAlertDialog(title, failedString, OKString, runnable);
334    }
335
336    public void showWaitingDialog(String title) {
337        mDialogHelper.showWaitingDialog(title);
338    }
339
340    public void dismissAllDialogs() {
341        mDialogHelper.dismissAll();
342    }
343
344    private void createContentView() {
345        LayoutInflater inflator = (LayoutInflater) mActivity
346                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
347        inflator.inflate(R.layout.panorama_module, mRootView, true);
348
349        Resources appRes = mActivity.getResources();
350        mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
351        mReviewBackground = appRes.getColor(R.color.review_background);
352        mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
353
354        mPreviewCover = mRootView.findViewById(R.id.preview_cover);
355        mPreviewLayout = mRootView.findViewById(R.id.pano_preview_layout);
356        mReviewControl = (ViewGroup) mRootView.findViewById(R.id.pano_review_control);
357        mReviewLayout = mRootView.findViewById(R.id.pano_review_layout);
358        mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea);
359        mCaptureLayout = (FrameLayout) mRootView.findViewById(R.id.panorama_capture_layout);
360        mCaptureProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar);
361        mCaptureProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
362        mCaptureProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
363        mCaptureProgressBar.setIndicatorColor(mIndicatorColor);
364        mCaptureProgressBar.setIndicatorWidth(20);
365
366        mPreviewBorder = mCaptureLayout.findViewById(R.id.pano_preview_area_border);
367
368        mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator);
369        mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator);
370        mLeftIndicator.setEnabled(false);
371        mRightIndicator.setEnabled(false);
372        mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview);
373        mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator);
374
375        mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
376        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
377        mShutterButton.setOnShutterButtonListener(this);
378        // Hide menu and indicators.
379        mRootView.findViewById(R.id.menu).setVisibility(View.GONE);
380        mRootView.findViewById(R.id.on_screen_indicators).setVisibility(View.GONE);
381        mReview.setBackgroundColor(mReviewBackground);
382
383        // TODO: set display change listener properly.
384        ((CameraRootView) mRootView).setDisplayChangeListener(null);
385        mTextureView = (TextureView) mRootView.findViewById(R.id.pano_preview_textureview);
386        mTextureView.setSurfaceTextureListener(this);
387        mTextureView.addOnLayoutChangeListener(this);
388        mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
389
390        mDialogHelper = new DialogHelper();
391        setViews(appRes);
392    }
393
394    private void setViews(Resources appRes) {
395        int weight = appRes.getInteger(R.integer.SRI_pano_layout_weight);
396
397        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mPreviewLayout.getLayoutParams();
398        lp.weight = weight;
399        mPreviewLayout.setLayoutParams(lp);
400
401        lp = (LinearLayout.LayoutParams) mReview.getLayoutParams();
402        lp.weight = weight;
403        mPreviewLayout.setLayoutParams(lp);
404
405        mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar);
406        mSavingProgressBar.setIndicatorWidth(0);
407        mSavingProgressBar.setMaxProgress(100);
408        mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
409        mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
410
411        View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button);
412        cancelButton.setOnClickListener(new View.OnClickListener() {
413            @Override
414            public void onClick(View arg0) {
415                mController.cancelHighResStitching();
416            }
417        });
418
419
420    }
421
422    private void showTooFastIndication() {
423        mTooFastPrompt.setVisibility(View.VISIBLE);
424        // The PreviewArea also contains the border for "too fast" indication.
425        mPreviewBorder.setVisibility(View.VISIBLE);
426        mCaptureProgressBar.setIndicatorColor(mIndicatorColorFast);
427        mLeftIndicator.setEnabled(true);
428        mRightIndicator.setEnabled(true);
429    }
430
431    private void hideTooFastIndication() {
432        mTooFastPrompt.setVisibility(View.GONE);
433        mPreviewBorder.setVisibility(View.INVISIBLE);
434        mCaptureProgressBar.setIndicatorColor(mIndicatorColor);
435        mLeftIndicator.setEnabled(false);
436        mRightIndicator.setEnabled(false);
437    }
438
439    public void flipPreviewIfNeeded() {
440        // Rotation needed to display image correctly clockwise
441        int cameraOrientation = mController.getCameraOrientation();
442        // Display rotated counter-clockwise
443        int displayRotation = CameraUtil.getDisplayRotation(mActivity);
444        // Rotation needed to display image correctly on current display
445        int rotation = (cameraOrientation - displayRotation + 360) % 360;
446        if (rotation >= 180) {
447            mTextureView.setRotation(180);
448        } else {
449            mTextureView.setRotation(0);
450        }
451    }
452
453    @Override
454    public void onDisplayChanged() {
455        mCameraControls.checkLayoutFlip();
456        flipPreviewIfNeeded();
457    }
458
459    public void initDisplayChangeListener() {
460        ((CameraRootView) mRootView).setDisplayChangeListener(this);
461    }
462
463    public void removeDisplayChangeListener() {
464        ((CameraRootView) mRootView).removeDisplayChangeListener();
465    }
466
467    public void showPreviewCover() {
468        mPreviewCover.setVisibility(View.VISIBLE);
469    }
470
471    private class DialogHelper {
472        private ProgressDialog mProgressDialog;
473        private AlertDialog mAlertDialog;
474
475        DialogHelper() {
476            mProgressDialog = null;
477            mAlertDialog = null;
478        }
479
480        public void dismissAll() {
481            if (mAlertDialog != null) {
482                mAlertDialog.dismiss();
483                mAlertDialog = null;
484            }
485            if (mProgressDialog != null) {
486                mProgressDialog.dismiss();
487                mProgressDialog = null;
488            }
489        }
490
491        public void showAlertDialog(
492                CharSequence title, CharSequence message,
493                CharSequence buttonMessage, final Runnable buttonRunnable) {
494            dismissAll();
495            mAlertDialog = (new AlertDialog.Builder(mActivity))
496                    .setTitle(title)
497                    .setMessage(message)
498                    .setNeutralButton(buttonMessage, new DialogInterface.OnClickListener() {
499                        @Override
500                        public void onClick(DialogInterface dialogInterface, int i) {
501                            buttonRunnable.run();
502                        }
503                    })
504                    .show();
505        }
506
507        public void showWaitingDialog(CharSequence message) {
508            dismissAll();
509            mProgressDialog = ProgressDialog.show(mActivity, null, message, true, false);
510        }
511    }
512
513    private static class FlipBitmapDrawable extends BitmapDrawable {
514
515        public FlipBitmapDrawable(Resources res, Bitmap bitmap) {
516            super(res, bitmap);
517        }
518
519        @Override
520        public void draw(Canvas canvas) {
521            Rect bounds = getBounds();
522            int cx = bounds.centerX();
523            int cy = bounds.centerY();
524            canvas.save(Canvas.MATRIX_SAVE_FLAG);
525            canvas.rotate(180, cx, cy);
526            super.draw(canvas);
527            canvas.restore();
528        }
529    }
530}
531