1/*
2 * Copyright (C) 2012 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.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.content.pm.ActivityInfo;
27import android.content.res.Configuration;
28import android.graphics.drawable.Drawable;
29import android.os.Bundle;
30import android.os.IBinder;
31import android.provider.MediaStore;
32import android.provider.Settings;
33import android.view.KeyEvent;
34import android.view.MotionEvent;
35import android.view.OrientationEventListener;
36import android.view.View;
37import android.view.Window;
38import android.view.WindowManager;
39import android.widget.FrameLayout;
40
41import com.android.camera.ui.CameraSwitcher;
42import com.android.gallery3d.R;
43import com.android.gallery3d.app.PhotoPage;
44import com.android.gallery3d.common.ApiHelper;
45import com.android.gallery3d.util.LightCycleHelper;
46
47public class CameraActivity extends ActivityBase
48        implements CameraSwitcher.CameraSwitchListener {
49    public static final int PHOTO_MODULE_INDEX = 0;
50    public static final int VIDEO_MODULE_INDEX = 1;
51    public static final int PANORAMA_MODULE_INDEX = 2;
52    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
53
54    CameraModule mCurrentModule;
55    private FrameLayout mFrame;
56    private ShutterButton mShutter;
57    private CameraSwitcher mSwitcher;
58    private View mCameraControls;
59    private View mControlsBackground;
60    private View mPieMenuButton;
61    private Drawable[] mDrawables;
62    private int mCurrentModuleIndex;
63    private MotionEvent mDown;
64    private boolean mAutoRotateScreen;
65    private int mHeightOrWidth = -1;
66
67    private MyOrientationEventListener mOrientationListener;
68    // The degrees of the device rotated clockwise from its natural orientation.
69    private int mLastRawOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
70
71    private MediaSaveService mMediaSaveService;
72    private ServiceConnection mConnection = new ServiceConnection() {
73            @Override
74            public void onServiceConnected(ComponentName className, IBinder b) {
75                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
76                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
77            }
78            @Override
79            public void onServiceDisconnected(ComponentName className) {
80                mMediaSaveService = null;
81            }};
82
83    private static final String TAG = "CAM_activity";
84
85    private static final int[] DRAW_IDS = {
86            R.drawable.ic_switch_camera,
87            R.drawable.ic_switch_video,
88            R.drawable.ic_switch_pan,
89            R.drawable.ic_switch_photosphere
90    };
91
92    @Override
93    public void onCreate(Bundle state) {
94        super.onCreate(state);
95        setContentView(R.layout.camera_main);
96        mFrame = (FrameLayout) findViewById(R.id.camera_app_root);
97        mDrawables = new Drawable[DRAW_IDS.length];
98        for (int i = 0; i < DRAW_IDS.length; i++) {
99            mDrawables[i] = getResources().getDrawable(DRAW_IDS[i]);
100        }
101        init();
102        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
103                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
104            mCurrentModule = new VideoModule();
105            mCurrentModuleIndex = VIDEO_MODULE_INDEX;
106        } else {
107            mCurrentModule = new PhotoModule();
108            mCurrentModuleIndex = PHOTO_MODULE_INDEX;
109        }
110        mCurrentModule.init(this, mFrame, true);
111        mSwitcher.setCurrentIndex(mCurrentModuleIndex);
112        mOrientationListener = new MyOrientationEventListener(this);
113        bindMediaSaveService();
114    }
115
116    public void init() {
117        boolean landscape = Util.getDisplayRotation(this) % 180 == 90;
118        mControlsBackground = findViewById(R.id.blocker);
119        mCameraControls = findViewById(R.id.camera_controls);
120        mShutter = (ShutterButton) findViewById(R.id.shutter_button);
121        mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher);
122        mPieMenuButton = findViewById(R.id.menu);
123        int totaldrawid = (LightCycleHelper.hasLightCycleCapture(this)
124                                ? DRAW_IDS.length : DRAW_IDS.length - 1);
125        if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
126
127        int[] drawids = new int[totaldrawid];
128        int[] moduleids = new int[totaldrawid];
129        int ix = 0;
130        for (int i = 0; i < mDrawables.length; i++) {
131            if (i == PANORAMA_MODULE_INDEX && !ApiHelper.HAS_OLD_PANORAMA) {
132                continue; // not enabled, so don't add to UI
133            }
134            if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(this)) {
135                continue; // not enabled, so don't add to UI
136            }
137            moduleids[ix] = i;
138            drawids[ix++] = DRAW_IDS[i];
139        }
140        mSwitcher.setIds(moduleids, drawids);
141        mSwitcher.setSwitchListener(this);
142        mSwitcher.setCurrentIndex(mCurrentModuleIndex);
143    }
144
145    @Override
146    public void onDestroy() {
147        unbindMediaSaveService();
148        super.onDestroy();
149    }
150
151    // Return whether the auto-rotate screen in system settings
152    // is turned on.
153    public boolean isAutoRotateScreen() {
154        return mAutoRotateScreen;
155    }
156
157    private class MyOrientationEventListener
158            extends OrientationEventListener {
159        public MyOrientationEventListener(Context context) {
160            super(context);
161        }
162
163        @Override
164        public void onOrientationChanged(int orientation) {
165            // We keep the last known orientation. So if the user first orient
166            // the camera then point the camera to floor or sky, we still have
167            // the correct orientation.
168            if (orientation == ORIENTATION_UNKNOWN) return;
169            mLastRawOrientation = orientation;
170            mCurrentModule.onOrientationChanged(orientation);
171        }
172    }
173
174    private ObjectAnimator mCameraSwitchAnimator;
175
176    @Override
177    public void onCameraSelected(final int i) {
178        if (mPaused) return;
179        if (i != mCurrentModuleIndex) {
180            mPaused = true;
181            CameraScreenNail screenNail = getCameraScreenNail();
182            if (screenNail != null) {
183                if (mCameraSwitchAnimator != null && mCameraSwitchAnimator.isRunning()) {
184                    mCameraSwitchAnimator.cancel();
185                }
186                mCameraSwitchAnimator = ObjectAnimator.ofFloat(
187                        screenNail, "alpha", screenNail.getAlpha(), 0f);
188                mCameraSwitchAnimator.addListener(new AnimatorListenerAdapter() {
189                    @Override
190                    public void onAnimationEnd(Animator animation) {
191                        super.onAnimationEnd(animation);
192                        doChangeCamera(i);
193                    }
194                });
195                mCameraSwitchAnimator.start();
196            } else {
197                doChangeCamera(i);
198            }
199        }
200    }
201
202    private void doChangeCamera(int i) {
203        boolean canReuse = canReuseScreenNail();
204        CameraHolder.instance().keep();
205        closeModule(mCurrentModule);
206        mCurrentModuleIndex = i;
207        switch (i) {
208            case VIDEO_MODULE_INDEX:
209                mCurrentModule = new VideoModule();
210                break;
211            case PHOTO_MODULE_INDEX:
212                mCurrentModule = new PhotoModule();
213                break;
214            case PANORAMA_MODULE_INDEX:
215                mCurrentModule = new PanoramaModule();
216                break;
217            case LIGHTCYCLE_MODULE_INDEX:
218                mCurrentModule = LightCycleHelper.createPanoramaModule();
219                break;
220        }
221        showPieMenuButton(mCurrentModule.needsPieMenu());
222
223        openModule(mCurrentModule, canReuse);
224        mCurrentModule.onOrientationChanged(mLastRawOrientation);
225        if (mMediaSaveService != null) {
226            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
227        }
228        getCameraScreenNail().setAlpha(0f);
229        getCameraScreenNail().setOnFrameDrawnOneShot(mOnFrameDrawn);
230    }
231
232    public void showPieMenuButton(boolean show) {
233        if (show) {
234            findViewById(R.id.blocker).setVisibility(View.VISIBLE);
235            findViewById(R.id.menu).setVisibility(View.VISIBLE);
236            findViewById(R.id.on_screen_indicators).setVisibility(View.VISIBLE);
237        } else {
238            findViewById(R.id.blocker).setVisibility(View.INVISIBLE);
239            findViewById(R.id.menu).setVisibility(View.INVISIBLE);
240            findViewById(R.id.on_screen_indicators).setVisibility(View.INVISIBLE);
241        }
242    }
243
244    private Runnable mOnFrameDrawn = new Runnable() {
245
246        @Override
247        public void run() {
248            runOnUiThread(mFadeInCameraScreenNail);
249        }
250    };
251
252    private Runnable mFadeInCameraScreenNail = new Runnable() {
253
254        @Override
255        public void run() {
256            mCameraSwitchAnimator = ObjectAnimator.ofFloat(
257                    getCameraScreenNail(), "alpha", 0f, 1f);
258            mCameraSwitchAnimator.setStartDelay(50);
259            mCameraSwitchAnimator.start();
260        }
261    };
262
263    @Override
264    public void onShowSwitcherPopup() {
265        mCurrentModule.onShowSwitcherPopup();
266    }
267
268    private void openModule(CameraModule module, boolean canReuse) {
269        module.init(this, mFrame, canReuse && canReuseScreenNail());
270        mPaused = false;
271        module.onResumeBeforeSuper();
272        module.onResumeAfterSuper();
273    }
274
275    private void closeModule(CameraModule module) {
276        module.onPauseBeforeSuper();
277        module.onPauseAfterSuper();
278        mFrame.removeAllViews();
279    }
280
281    public ShutterButton getShutterButton() {
282        return mShutter;
283    }
284
285    public void hideUI() {
286        mCameraControls.setVisibility(View.INVISIBLE);
287        hideSwitcher();
288        mShutter.setVisibility(View.GONE);
289    }
290
291    public void showUI() {
292        mCameraControls.setVisibility(View.VISIBLE);
293        showSwitcher();
294        mShutter.setVisibility(View.VISIBLE);
295        // Force a layout change to show shutter button
296        mShutter.requestLayout();
297    }
298
299    public void hideSwitcher() {
300        mSwitcher.closePopup();
301        mSwitcher.setVisibility(View.INVISIBLE);
302    }
303
304    public void showSwitcher() {
305        if (mCurrentModule.needsSwitcher()) {
306            mSwitcher.setVisibility(View.VISIBLE);
307        }
308    }
309
310    public boolean isInCameraApp() {
311        return mShowCameraAppView;
312    }
313
314    @Override
315    public void onConfigurationChanged(Configuration config) {
316        super.onConfigurationChanged(config);
317        mCurrentModule.onConfigurationChanged(config);
318    }
319
320    @Override
321    public void onPause() {
322        mPaused = true;
323        mOrientationListener.disable();
324        mCurrentModule.onPauseBeforeSuper();
325        super.onPause();
326        mCurrentModule.onPauseAfterSuper();
327    }
328
329    @Override
330    public void onResume() {
331        mPaused = false;
332        if (Settings.System.getInt(getContentResolver(),
333                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
334            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
335            mAutoRotateScreen = false;
336        } else {
337            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
338            mAutoRotateScreen = true;
339        }
340        mOrientationListener.enable();
341        mCurrentModule.onResumeBeforeSuper();
342        super.onResume();
343        mCurrentModule.onResumeAfterSuper();
344    }
345
346    private void bindMediaSaveService() {
347        Intent intent = new Intent(this, MediaSaveService.class);
348        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
349    }
350
351    private void unbindMediaSaveService() {
352        if (mMediaSaveService != null) {
353            mMediaSaveService.setListener(null);
354        }
355        if (mConnection != null) {
356            unbindService(mConnection);
357        }
358    }
359
360    @Override
361    protected void onFullScreenChanged(boolean full) {
362        if (full) {
363            showUI();
364        } else {
365            hideUI();
366        }
367        super.onFullScreenChanged(full);
368        if (ApiHelper.HAS_ROTATION_ANIMATION) {
369            setRotationAnimation(full);
370        }
371        mCurrentModule.onFullScreenChanged(full);
372    }
373
374    private void setRotationAnimation(boolean fullscreen) {
375        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
376        if (fullscreen) {
377            rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
378        }
379        Window win = getWindow();
380        WindowManager.LayoutParams winParams = win.getAttributes();
381        winParams.rotationAnimation = rotationAnimation;
382        win.setAttributes(winParams);
383    }
384
385    @Override
386    protected void onStop() {
387        super.onStop();
388        mCurrentModule.onStop();
389        getStateManager().clearTasks();
390    }
391
392    @Override
393    protected void onNewIntent(Intent intent) {
394        super.onNewIntent(intent);
395        getStateManager().clearActivityResult();
396    }
397
398    @Override
399    protected void installIntentFilter() {
400        super.installIntentFilter();
401        mCurrentModule.installIntentFilter();
402    }
403
404    @Override
405    protected void onActivityResult(
406            int requestCode, int resultCode, Intent data) {
407        // Only PhotoPage understands ProxyLauncher.RESULT_USER_CANCELED
408        if (resultCode == ProxyLauncher.RESULT_USER_CANCELED
409                && !(getStateManager().getTopState() instanceof PhotoPage)) {
410            resultCode = RESULT_CANCELED;
411        }
412        super.onActivityResult(requestCode, resultCode, data);
413        // Unmap cancel vs. reset
414        if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) {
415            resultCode = RESULT_CANCELED;
416        }
417        mCurrentModule.onActivityResult(requestCode, resultCode, data);
418    }
419
420    // Preview area is touched. Handle touch focus.
421    // Touch to focus is handled by PreviewGestures, this function call
422    // is no longer needed. TODO: Clean it up in the next refactor
423    @Override
424    protected void onSingleTapUp(View view, int x, int y) {
425    }
426
427    @Override
428    public void onBackPressed() {
429        if (!mCurrentModule.onBackPressed()) {
430            super.onBackPressed();
431        }
432    }
433
434    @Override
435    public boolean onKeyDown(int keyCode, KeyEvent event) {
436        return mCurrentModule.onKeyDown(keyCode,  event)
437                || super.onKeyDown(keyCode, event);
438    }
439
440    @Override
441    public boolean onKeyUp(int keyCode, KeyEvent event) {
442        return mCurrentModule.onKeyUp(keyCode,  event)
443                || super.onKeyUp(keyCode, event);
444    }
445
446    public void cancelActivityTouchHandling() {
447        if (mDown != null) {
448            MotionEvent cancel = MotionEvent.obtain(mDown);
449            cancel.setAction(MotionEvent.ACTION_CANCEL);
450            super.dispatchTouchEvent(cancel);
451        }
452    }
453
454    @Override
455    public boolean dispatchTouchEvent(MotionEvent m) {
456        if (m.getActionMasked() == MotionEvent.ACTION_DOWN) {
457            mDown = m;
458        }
459        if ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) {
460            return mSwitcher.onTouch(null, m);
461        } else if ((mSwitcher != null) && mSwitcher.isInsidePopup(m)) {
462            return superDispatchTouchEvent(m);
463        } else {
464            return mCurrentModule.dispatchTouchEvent(m);
465        }
466    }
467
468    @Override
469    public void startActivityForResult(Intent intent, int requestCode) {
470        Intent proxyIntent = new Intent(this, ProxyLauncher.class);
471        proxyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
472        proxyIntent.putExtra(Intent.EXTRA_INTENT, intent);
473        super.startActivityForResult(proxyIntent, requestCode);
474    }
475
476    public boolean superDispatchTouchEvent(MotionEvent m) {
477        return super.dispatchTouchEvent(m);
478    }
479
480    // Preview texture has been copied. Now camera can be released and the
481    // animation can be started.
482    @Override
483    public void onPreviewTextureCopied() {
484        mCurrentModule.onPreviewTextureCopied();
485    }
486
487    @Override
488    public void onCaptureTextureCopied() {
489        mCurrentModule.onCaptureTextureCopied();
490    }
491
492    @Override
493    public void onUserInteraction() {
494        super.onUserInteraction();
495        mCurrentModule.onUserInteraction();
496    }
497
498    @Override
499    protected boolean updateStorageHintOnResume() {
500        return mCurrentModule.updateStorageHintOnResume();
501    }
502
503    @Override
504    public void updateCameraAppView() {
505        super.updateCameraAppView();
506        mCurrentModule.updateCameraAppView();
507    }
508
509    private boolean canReuseScreenNail() {
510        return mCurrentModuleIndex == PHOTO_MODULE_INDEX
511                || mCurrentModuleIndex == VIDEO_MODULE_INDEX
512                || mCurrentModuleIndex == LIGHTCYCLE_MODULE_INDEX;
513    }
514
515    @Override
516    public boolean isPanoramaActivity() {
517        return (mCurrentModuleIndex == PANORAMA_MODULE_INDEX);
518    }
519
520    // Accessor methods for getting latency times used in performance testing
521    public long getAutoFocusTime() {
522        return (mCurrentModule instanceof PhotoModule) ?
523                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
524    }
525
526    public long getShutterLag() {
527        return (mCurrentModule instanceof PhotoModule) ?
528                ((PhotoModule) mCurrentModule).mShutterLag : -1;
529    }
530
531    public long getShutterToPictureDisplayedTime() {
532        return (mCurrentModule instanceof PhotoModule) ?
533                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
534    }
535
536    public long getPictureDisplayedToJpegCallbackTime() {
537        return (mCurrentModule instanceof PhotoModule) ?
538                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
539    }
540
541    public long getJpegCallbackFinishTime() {
542        return (mCurrentModule instanceof PhotoModule) ?
543                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
544    }
545
546    public long getCaptureStartTime() {
547        return (mCurrentModule instanceof PhotoModule) ?
548                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
549    }
550
551    public boolean isRecording() {
552        return (mCurrentModule instanceof VideoModule) ?
553                ((VideoModule) mCurrentModule).isRecording() : false;
554    }
555
556    public CameraScreenNail getCameraScreenNail() {
557        return (CameraScreenNail) mCameraScreenNail;
558    }
559
560    public MediaSaveService getMediaSaveService() {
561        return mMediaSaveService;
562    }
563}
564