1/*
2 * Copyright (C) 2009 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.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.graphics.Rect;
26import android.hardware.Camera.Parameters;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.Message;
31import android.view.KeyEvent;
32import android.view.View;
33import android.view.Window;
34import android.view.WindowManager;
35import android.view.animation.AlphaAnimation;
36import android.view.animation.Animation;
37import android.view.animation.DecelerateInterpolator;
38
39import com.android.camera.ui.LayoutChangeNotifier;
40import com.android.camera.ui.PopupManager;
41import com.android.gallery3d.app.AbstractGalleryActivity;
42import com.android.gallery3d.app.AppBridge;
43import com.android.gallery3d.app.FilmstripPage;
44import com.android.gallery3d.app.GalleryActionBar;
45import com.android.gallery3d.app.PhotoPage;
46import com.android.gallery3d.common.ApiHelper;
47import com.android.gallery3d.ui.ScreenNail;
48import com.android.gallery3d.util.MediaSetUtils;
49
50/**
51 * Superclass of camera activity.
52 */
53public abstract class ActivityBase extends AbstractGalleryActivity
54        implements LayoutChangeNotifier.Listener {
55
56    private static final String TAG = "ActivityBase";
57    private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
58    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
59            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
60    public static final String ACTION_IMAGE_CAPTURE_SECURE =
61            "android.media.action.IMAGE_CAPTURE_SECURE";
62    // The intent extra for camera from secure lock screen. True if the gallery
63    // should only show newly captured pictures. sSecureAlbumId does not
64    // increment. This is used when switching between camera, camcorder, and
65    // panorama. If the extra is not set, it is in the normal camera mode.
66    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
67
68    private int mResultCodeForTesting;
69    private Intent mResultDataForTesting;
70    private OnScreenHint mStorageHint;
71    private View mSingleTapArea;
72
73    protected boolean mOpenCameraFail;
74    protected boolean mCameraDisabled;
75    protected CameraManager.CameraProxy mCameraDevice;
76    protected Parameters mParameters;
77    // The activity is paused. The classes that extend this class should set
78    // mPaused the first thing in onResume/onPause.
79    protected boolean mPaused;
80    protected GalleryActionBar mActionBar;
81
82    // multiple cameras support
83    protected int mNumberOfCameras;
84    protected int mCameraId;
85    // The activity is going to switch to the specified camera id. This is
86    // needed because texture copy is done in GL thread. -1 means camera is not
87    // switching.
88    protected int mPendingSwitchCameraId = -1;
89
90    protected MyAppBridge mAppBridge;
91    protected ScreenNail mCameraScreenNail; // This shows camera preview.
92    // The view containing only camera related widgets like control panel,
93    // indicator bar, focus indicator and etc.
94    protected View mCameraAppView;
95    protected boolean mShowCameraAppView = true;
96    private Animation mCameraAppViewFadeIn;
97    private Animation mCameraAppViewFadeOut;
98    // Secure album id. This should be incremented every time the camera is
99    // launched from the secure lock screen. The id should be the same when
100    // switching between camera, camcorder, and panorama.
101    protected static int sSecureAlbumId;
102    // True if the camera is started from secure lock screen.
103    protected boolean mSecureCamera;
104    private static boolean sFirstStartAfterScreenOn = true;
105
106    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
107    private static final int UPDATE_STORAGE_HINT = 0;
108    private final Handler mHandler = new Handler() {
109            @Override
110            public void handleMessage(Message msg) {
111                switch (msg.what) {
112                    case UPDATE_STORAGE_HINT:
113                        updateStorageHint();
114                        return;
115                }
116            }
117    };
118
119    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
120        @Override
121        public void onReceive(Context context, Intent intent) {
122            String action = intent.getAction();
123            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
124                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
125                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
126                    || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
127                updateStorageSpaceAndHint();
128            }
129        }
130    };
131
132    // close activity when screen turns off
133    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
134        @Override
135        public void onReceive(Context context, Intent intent) {
136            finish();
137        }
138    };
139
140    private static BroadcastReceiver sScreenOffReceiver;
141    private static class ScreenOffReceiver extends BroadcastReceiver {
142        @Override
143        public void onReceive(Context context, Intent intent) {
144            sFirstStartAfterScreenOn = true;
145        }
146    }
147
148    public static boolean isFirstStartAfterScreenOn() {
149        return sFirstStartAfterScreenOn;
150    }
151
152    public static void resetFirstStartAfterScreenOn() {
153        sFirstStartAfterScreenOn = false;
154    }
155
156    protected class CameraOpenThread extends Thread {
157        @Override
158        public void run() {
159            try {
160                mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
161                mParameters = mCameraDevice.getParameters();
162            } catch (CameraHardwareException e) {
163                mOpenCameraFail = true;
164            } catch (CameraDisabledException e) {
165                mCameraDisabled = true;
166            }
167        }
168    }
169
170    @Override
171    public void onCreate(Bundle icicle) {
172        super.disableToggleStatusBar();
173        // Set a theme with action bar. It is not specified in manifest because
174        // we want to hide it by default. setTheme must happen before
175        // setContentView.
176        //
177        // This must be set before we call super.onCreate(), where the window's
178        // background is removed.
179        setTheme(R.style.Theme_Gallery);
180        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
181        if (ApiHelper.HAS_ACTION_BAR) {
182            requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
183        } else {
184            requestWindowFeature(Window.FEATURE_NO_TITLE);
185        }
186
187        // Check if this is in the secure camera mode.
188        Intent intent = getIntent();
189        String action = intent.getAction();
190        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
191            mSecureCamera = true;
192            // Use a new album when this is started from the lock screen.
193            sSecureAlbumId++;
194        } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
195            mSecureCamera = true;
196        } else {
197            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
198        }
199        if (mSecureCamera) {
200            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
201            registerReceiver(mScreenOffReceiver, filter);
202            if (sScreenOffReceiver == null) {
203                sScreenOffReceiver = new ScreenOffReceiver();
204                getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
205            }
206        }
207        super.onCreate(icicle);
208    }
209
210    public boolean isPanoramaActivity() {
211        return false;
212    }
213
214    @Override
215    protected void onResume() {
216        super.onResume();
217
218        installIntentFilter();
219        if (updateStorageHintOnResume()) {
220            updateStorageSpace();
221            mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200);
222        }
223    }
224
225    @Override
226    protected void onPause() {
227        super.onPause();
228
229        if (mStorageHint != null) {
230            mStorageHint.cancel();
231            mStorageHint = null;
232        }
233
234        unregisterReceiver(mReceiver);
235    }
236
237    @Override
238    public void setContentView(int layoutResID) {
239        super.setContentView(layoutResID);
240        // getActionBar() should be after setContentView
241        mActionBar = new GalleryActionBar(this);
242        mActionBar.hide();
243    }
244
245    @Override
246    public boolean onSearchRequested() {
247        return false;
248    }
249
250    @Override
251    public boolean onKeyDown(int keyCode, KeyEvent event) {
252        // Prevent software keyboard or voice search from showing up.
253        if (keyCode == KeyEvent.KEYCODE_SEARCH
254                || keyCode == KeyEvent.KEYCODE_MENU) {
255            if (event.isLongPress()) return true;
256        }
257        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
258            return true;
259        }
260
261        return super.onKeyDown(keyCode, event);
262    }
263
264    @Override
265    public boolean onKeyUp(int keyCode, KeyEvent event) {
266        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
267            return true;
268        }
269        return super.onKeyUp(keyCode, event);
270    }
271
272    protected void setResultEx(int resultCode) {
273        mResultCodeForTesting = resultCode;
274        setResult(resultCode);
275    }
276
277    protected void setResultEx(int resultCode, Intent data) {
278        mResultCodeForTesting = resultCode;
279        mResultDataForTesting = data;
280        setResult(resultCode, data);
281    }
282
283    public int getResultCode() {
284        return mResultCodeForTesting;
285    }
286
287    public Intent getResultData() {
288        return mResultDataForTesting;
289    }
290
291    @Override
292    protected void onDestroy() {
293        PopupManager.removeInstance(this);
294        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
295        super.onDestroy();
296    }
297
298    protected void installIntentFilter() {
299        // install an intent filter to receive SD card related events.
300        IntentFilter intentFilter =
301                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
302        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
303        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
304        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
305        intentFilter.addDataScheme("file");
306        registerReceiver(mReceiver, intentFilter);
307    }
308
309    protected void updateStorageSpace() {
310        mStorageSpace = Storage.getAvailableSpace();
311    }
312
313    protected long getStorageSpace() {
314        return mStorageSpace;
315    }
316
317    protected void updateStorageSpaceAndHint() {
318        updateStorageSpace();
319        updateStorageHint(mStorageSpace);
320    }
321
322    protected void updateStorageHint() {
323        updateStorageHint(mStorageSpace);
324    }
325
326    protected boolean updateStorageHintOnResume() {
327        return true;
328    }
329
330    protected void updateStorageHint(long storageSpace) {
331        String message = null;
332        if (storageSpace == Storage.UNAVAILABLE) {
333            message = getString(R.string.no_storage);
334        } else if (storageSpace == Storage.PREPARING) {
335            message = getString(R.string.preparing_sd);
336        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
337            message = getString(R.string.access_sd_fail);
338        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
339            message = getString(R.string.spaceIsLow_content);
340        }
341
342        if (message != null) {
343            if (mStorageHint == null) {
344                mStorageHint = OnScreenHint.makeText(this, message);
345            } else {
346                mStorageHint.setText(message);
347            }
348            mStorageHint.show();
349        } else if (mStorageHint != null) {
350            mStorageHint.cancel();
351            mStorageHint = null;
352        }
353    }
354
355    protected void gotoGallery() {
356        // Move the next picture with capture animation. "1" means next.
357        mAppBridge.switchWithCaptureAnimation(1);
358    }
359
360    // Call this after setContentView.
361    public ScreenNail createCameraScreenNail(boolean getPictures) {
362        mCameraAppView = findViewById(R.id.camera_app_root);
363        Bundle data = new Bundle();
364        String path;
365        if (getPictures) {
366            if (mSecureCamera) {
367                path = "/secure/all/" + sSecureAlbumId;
368            } else {
369                path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
370            }
371        } else {
372            path = "/local/all/0"; // Use 0 so gallery does not show anything.
373        }
374        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
375        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
376        data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
377
378        // Send an AppBridge to gallery to enable the camera preview.
379        if (mAppBridge != null) {
380            mCameraScreenNail.recycle();
381        }
382        mAppBridge = new MyAppBridge();
383        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
384        if (getStateManager().getStateCount() == 0) {
385            getStateManager().startState(FilmstripPage.class, data);
386        } else {
387            getStateManager().switchState(getStateManager().getTopState(),
388                    FilmstripPage.class, data);
389        }
390        mCameraScreenNail = mAppBridge.getCameraScreenNail();
391        return mCameraScreenNail;
392    }
393
394    // Call this after setContentView.
395    protected ScreenNail reuseCameraScreenNail(boolean getPictures) {
396        mCameraAppView = findViewById(R.id.camera_app_root);
397        Bundle data = new Bundle();
398        String path;
399        if (getPictures) {
400            if (mSecureCamera) {
401                path = "/secure/all/" + sSecureAlbumId;
402            } else {
403                path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
404            }
405        } else {
406            path = "/local/all/0"; // Use 0 so gallery does not show anything.
407        }
408        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
409        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
410        data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
411
412        // Send an AppBridge to gallery to enable the camera preview.
413        if (mAppBridge == null) {
414            mAppBridge = new MyAppBridge();
415        }
416        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
417        if (getStateManager().getStateCount() == 0) {
418            getStateManager().startState(FilmstripPage.class, data);
419        }
420        mCameraScreenNail = mAppBridge.getCameraScreenNail();
421        return mCameraScreenNail;
422    }
423
424    private class HideCameraAppView implements Animation.AnimationListener {
425        @Override
426        public void onAnimationEnd(Animation animation) {
427            // We cannot set this as GONE because we want to receive the
428            // onLayoutChange() callback even when we are invisible.
429            mCameraAppView.setVisibility(View.INVISIBLE);
430        }
431
432        @Override
433        public void onAnimationRepeat(Animation animation) {
434        }
435
436        @Override
437        public void onAnimationStart(Animation animation) {
438        }
439    }
440
441    protected void updateCameraAppView() {
442        // Initialize the animation.
443        if (mCameraAppViewFadeIn == null) {
444            mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f);
445            mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
446            mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator());
447
448            mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f);
449            mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
450            mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator());
451            mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView());
452        }
453
454        if (mShowCameraAppView) {
455            mCameraAppView.setVisibility(View.VISIBLE);
456            // The "transparent region" is not recomputed when a sibling of
457            // SurfaceView changes visibility (unless it involves GONE). It's
458            // been broken since 1.0. Call requestLayout to work around it.
459            mCameraAppView.requestLayout();
460            mCameraAppView.startAnimation(mCameraAppViewFadeIn);
461        } else {
462            mCameraAppView.startAnimation(mCameraAppViewFadeOut);
463        }
464    }
465
466    protected void onFullScreenChanged(boolean full) {
467        if (mShowCameraAppView == full) return;
468        mShowCameraAppView = full;
469        if (mPaused || isFinishing()) return;
470        updateCameraAppView();
471    }
472
473    @Override
474    public GalleryActionBar getGalleryActionBar() {
475        return mActionBar;
476    }
477
478    // Preview frame layout has changed.
479    @Override
480    public void onLayoutChange(View v, int left, int top, int right, int bottom) {
481        if (mAppBridge == null) return;
482
483        int width = right - left;
484        int height = bottom - top;
485        if (ApiHelper.HAS_SURFACE_TEXTURE) {
486            CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail;
487            if (Util.getDisplayRotation(this) % 180 == 0) {
488                screenNail.setPreviewFrameLayoutSize(width, height);
489            } else {
490                // Swap the width and height. Camera screen nail draw() is based on
491                // natural orientation, not the view system orientation.
492                screenNail.setPreviewFrameLayoutSize(height, width);
493            }
494            notifyScreenNailChanged();
495        }
496    }
497
498    protected void setSingleTapUpListener(View singleTapArea) {
499        mSingleTapArea = singleTapArea;
500    }
501
502    private boolean onSingleTapUp(int x, int y) {
503        // Ignore if listener is null or the camera control is invisible.
504        if (mSingleTapArea == null || !mShowCameraAppView) return false;
505
506        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
507                mSingleTapArea);
508        x -= relativeLocation[0];
509        y -= relativeLocation[1];
510        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
511                && y < mSingleTapArea.getHeight()) {
512            onSingleTapUp(mSingleTapArea, x, y);
513            return true;
514        }
515        return false;
516    }
517
518    protected void onSingleTapUp(View view, int x, int y) {
519    }
520
521    public void setSwipingEnabled(boolean enabled) {
522        mAppBridge.setSwipingEnabled(enabled);
523    }
524
525    public void notifyScreenNailChanged() {
526        mAppBridge.notifyScreenNailChanged();
527    }
528
529    protected void onPreviewTextureCopied() {
530    }
531
532    protected void onCaptureTextureCopied() {
533    }
534
535    protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) {
536        if (mSecureCamera) {
537            int id = Integer.parseInt(uri.getLastPathSegment());
538            mAppBridge.addSecureAlbumItem(isVideo, id);
539        }
540    }
541
542    public boolean isSecureCamera() {
543        return mSecureCamera;
544    }
545
546    //////////////////////////////////////////////////////////////////////////
547    //  The is the communication interface between the Camera Application and
548    //  the Gallery PhotoPage.
549    //////////////////////////////////////////////////////////////////////////
550
551    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
552        @SuppressWarnings("hiding")
553        private ScreenNail mCameraScreenNail;
554        private Server mServer;
555
556        @Override
557        public ScreenNail attachScreenNail() {
558            if (mCameraScreenNail == null) {
559                if (ApiHelper.HAS_SURFACE_TEXTURE) {
560                    mCameraScreenNail = new CameraScreenNail(this);
561                } else {
562                    Bitmap b = BitmapFactory.decodeResource(getResources(),
563                            R.drawable.wallpaper_picker_preview);
564                    mCameraScreenNail = new StaticBitmapScreenNail(b);
565                }
566            }
567            return mCameraScreenNail;
568        }
569
570        @Override
571        public void detachScreenNail() {
572            mCameraScreenNail = null;
573        }
574
575        public ScreenNail getCameraScreenNail() {
576            return mCameraScreenNail;
577        }
578
579        // Return true if the tap is consumed.
580        @Override
581        public boolean onSingleTapUp(int x, int y) {
582            return ActivityBase.this.onSingleTapUp(x, y);
583        }
584
585        // This is used to notify that the screen nail will be drawn in full screen
586        // or not in next draw() call.
587        @Override
588        public void onFullScreenChanged(boolean full) {
589            ActivityBase.this.onFullScreenChanged(full);
590        }
591
592        @Override
593        public void requestRender() {
594            getGLRoot().requestRenderForced();
595        }
596
597        @Override
598        public void onPreviewTextureCopied() {
599            ActivityBase.this.onPreviewTextureCopied();
600        }
601
602        @Override
603        public void onCaptureTextureCopied() {
604            ActivityBase.this.onCaptureTextureCopied();
605        }
606
607        @Override
608        public void setServer(Server s) {
609            mServer = s;
610        }
611
612        @Override
613        public boolean isPanorama() {
614            return ActivityBase.this.isPanoramaActivity();
615        }
616
617        @Override
618        public boolean isStaticCamera() {
619            return !ApiHelper.HAS_SURFACE_TEXTURE;
620        }
621
622        public void addSecureAlbumItem(boolean isVideo, int id) {
623            if (mServer != null) mServer.addSecureAlbumItem(isVideo, id);
624        }
625
626        private void setCameraRelativeFrame(Rect frame) {
627            if (mServer != null) mServer.setCameraRelativeFrame(frame);
628        }
629
630        private void switchWithCaptureAnimation(int offset) {
631            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
632        }
633
634        private void setSwipingEnabled(boolean enabled) {
635            if (mServer != null) mServer.setSwipingEnabled(enabled);
636        }
637
638        private void notifyScreenNailChanged() {
639            if (mServer != null) mServer.notifyScreenNailChanged();
640        }
641    }
642}
643