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