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.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.graphics.Rect;
25import android.hardware.Camera.Parameters;
26import android.os.AsyncTask;
27import android.os.Bundle;
28import android.support.v4.content.LocalBroadcastManager;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.Menu;
32import android.view.View;
33import android.view.Window;
34import android.view.WindowManager;
35import android.view.animation.DecelerateInterpolator;
36
37import com.android.camera.ui.CameraPicker;
38import com.android.camera.ui.PopupManager;
39import com.android.camera.ui.RotateImageView;
40import com.android.gallery3d.app.AbstractGalleryActivity;
41import com.android.gallery3d.app.AppBridge;
42import com.android.gallery3d.app.GalleryActionBar;
43import com.android.gallery3d.app.PhotoPage;
44import com.android.gallery3d.ui.ScreenNail;
45import com.android.gallery3d.util.MediaSetUtils;
46
47import java.io.File;
48
49/**
50 * Superclass of Camera and VideoCamera activities.
51 */
52abstract public class ActivityBase extends AbstractGalleryActivity
53        implements View.OnLayoutChangeListener {
54
55    private static final String TAG = "ActivityBase";
56    private static final boolean LOGV = false;
57    private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
58    private static final String ACTION_DELETE_PICTURE =
59            "com.android.gallery3d.action.DELETE_PICTURE";
60    private int mResultCodeForTesting;
61    private Intent mResultDataForTesting;
62    private OnScreenHint mStorageHint;
63    private HideCameraAppView mHideCameraAppView;
64    private View mSingleTapArea;
65
66    // The bitmap of the last captured picture thumbnail and the URI of the
67    // original picture.
68    protected Thumbnail mThumbnail;
69    protected int mThumbnailViewWidth; // layout width of the thumbnail
70    protected AsyncTask<Void, Void, Thumbnail> mLoadThumbnailTask;
71    // An imageview showing the last captured picture thumbnail.
72    protected RotateImageView mThumbnailView;
73    protected CameraPicker mCameraPicker;
74
75    protected boolean mOpenCameraFail;
76    protected boolean mCameraDisabled;
77    protected CameraManager.CameraProxy mCameraDevice;
78    protected Parameters mParameters;
79    // The activity is paused. The classes that extend this class should set
80    // mPaused the first thing in onResume/onPause.
81    protected boolean mPaused;
82    protected GalleryActionBar mActionBar;
83
84    // multiple cameras support
85    protected int mNumberOfCameras;
86    protected int mCameraId;
87    // The activity is going to switch to the specified camera id. This is
88    // needed because texture copy is done in GL thread. -1 means camera is not
89    // switching.
90    protected int mPendingSwitchCameraId = -1;
91
92    protected MyAppBridge mAppBridge;
93    protected CameraScreenNail mCameraScreenNail; // This shows camera preview.
94    // The view containing only camera related widgets like control panel,
95    // indicator bar, focus indicator and etc.
96    protected View mCameraAppView;
97    protected boolean mShowCameraAppView = true;
98    private boolean mUpdateThumbnailDelayed;
99    private IntentFilter mDeletePictureFilter =
100            new IntentFilter(ACTION_DELETE_PICTURE);
101    private BroadcastReceiver mDeletePictureReceiver =
102            new BroadcastReceiver() {
103                @Override
104                public void onReceive(Context context, Intent intent) {
105                    if (mShowCameraAppView) {
106                        getLastThumbnailUncached();
107                    } else {
108                        mUpdateThumbnailDelayed = true;
109                    }
110                }
111            };
112
113    protected class CameraOpenThread extends Thread {
114        @Override
115        public void run() {
116            try {
117                mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
118                mParameters = mCameraDevice.getParameters();
119            } catch (CameraHardwareException e) {
120                mOpenCameraFail = true;
121            } catch (CameraDisabledException e) {
122                mCameraDisabled = true;
123            }
124        }
125    }
126
127    @Override
128    public void onCreate(Bundle icicle) {
129        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
130        super.disableToggleStatusBar();
131        // Set a theme with action bar. It is not specified in manifest because
132        // we want to hide it by default. setTheme must happen before
133        // setContentView.
134        //
135        // This must be set before we call super.onCreate(), where the window's
136        // background is removed.
137        setTheme(R.style.Theme_Gallery);
138        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
139        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
140
141        super.onCreate(icicle);
142    }
143
144    public boolean isPanoramaActivity() {
145        return false;
146    }
147
148    @Override
149    protected void onResume() {
150        super.onResume();
151        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
152        manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter);
153    }
154
155    @Override
156    protected void onPause() {
157        super.onPause();
158        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
159        manager.unregisterReceiver(mDeletePictureReceiver);
160
161        if (LOGV) Log.v(TAG, "onPause");
162        saveThumbnailToFile();
163
164        if (mLoadThumbnailTask != null) {
165            mLoadThumbnailTask.cancel(true);
166            mLoadThumbnailTask = null;
167        }
168
169        if (mStorageHint != null) {
170            mStorageHint.cancel();
171            mStorageHint = null;
172        }
173    }
174
175    @Override
176    public void setContentView(int layoutResID) {
177        super.setContentView(layoutResID);
178        // getActionBar() should be after setContentView
179        mActionBar = new GalleryActionBar(this);
180        mActionBar.hide();
181    }
182
183    @Override
184    public boolean onSearchRequested() {
185        return false;
186    }
187
188    @Override
189    public boolean onKeyDown(int keyCode, KeyEvent event) {
190        // Prevent software keyboard or voice search from showing up.
191        if (keyCode == KeyEvent.KEYCODE_SEARCH
192                || keyCode == KeyEvent.KEYCODE_MENU) {
193            if (event.isLongPress()) return true;
194        }
195
196        return super.onKeyDown(keyCode, event);
197    }
198
199    protected void setResultEx(int resultCode) {
200        mResultCodeForTesting = resultCode;
201        setResult(resultCode);
202    }
203
204    protected void setResultEx(int resultCode, Intent data) {
205        mResultCodeForTesting = resultCode;
206        mResultDataForTesting = data;
207        setResult(resultCode, data);
208    }
209
210    public int getResultCode() {
211        return mResultCodeForTesting;
212    }
213
214    public Intent getResultData() {
215        return mResultDataForTesting;
216    }
217
218    @Override
219    protected void onDestroy() {
220        PopupManager.removeInstance(this);
221        super.onDestroy();
222    }
223
224    @Override
225    public boolean onCreateOptionsMenu(Menu menu) {
226        super.onCreateOptionsMenu(menu);
227        return getStateManager().createOptionsMenu(menu);
228    }
229
230    protected void updateStorageHint(long storageSpace) {
231        String message = null;
232        if (storageSpace == Storage.UNAVAILABLE) {
233            message = getString(R.string.no_storage);
234        } else if (storageSpace == Storage.PREPARING) {
235            message = getString(R.string.preparing_sd);
236        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
237            message = getString(R.string.access_sd_fail);
238        } else if (storageSpace < Storage.LOW_STORAGE_THRESHOLD) {
239            message = getString(R.string.spaceIsLow_content);
240        }
241
242        if (message != null) {
243            if (mStorageHint == null) {
244                mStorageHint = OnScreenHint.makeText(this, message);
245            } else {
246                mStorageHint.setText(message);
247            }
248            mStorageHint.show();
249        } else if (mStorageHint != null) {
250            mStorageHint.cancel();
251            mStorageHint = null;
252        }
253    }
254
255    protected void updateThumbnailView() {
256        if (mThumbnail != null) {
257            mThumbnailView.setBitmap(mThumbnail.getBitmap());
258            mThumbnailView.setVisibility(View.VISIBLE);
259        } else {
260            mThumbnailView.setBitmap(null);
261            mThumbnailView.setVisibility(View.GONE);
262        }
263    }
264
265    protected void getLastThumbnail() {
266        mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver());
267        // Suppose users tap the thumbnail view, go to the gallery, delete the
268        // image, and coming back to the camera. Thumbnail file will be invalid.
269        // Since the new thumbnail will be loaded in another thread later, the
270        // view should be set to gone to prevent from opening the invalid image.
271        updateThumbnailView();
272        if (mThumbnail == null) {
273            mLoadThumbnailTask = new LoadThumbnailTask(true).execute();
274        }
275    }
276
277    protected void getLastThumbnailUncached() {
278        if (mLoadThumbnailTask != null) mLoadThumbnailTask.cancel(true);
279        mLoadThumbnailTask = new LoadThumbnailTask(false).execute();
280    }
281
282    private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> {
283        private boolean mLookAtCache;
284
285        public LoadThumbnailTask(boolean lookAtCache) {
286            mLookAtCache = lookAtCache;
287        }
288
289        @Override
290        protected Thumbnail doInBackground(Void... params) {
291            // Load the thumbnail from the file.
292            ContentResolver resolver = getContentResolver();
293            Thumbnail t = null;
294            if (mLookAtCache) {
295                t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver);
296            }
297
298            if (isCancelled()) return null;
299
300            if (t == null) {
301                Thumbnail result[] = new Thumbnail[1];
302                // Load the thumbnail from the media provider.
303                int code = Thumbnail.getLastThumbnailFromContentResolver(
304                        resolver, result);
305                switch (code) {
306                    case Thumbnail.THUMBNAIL_FOUND:
307                        return result[0];
308                    case Thumbnail.THUMBNAIL_NOT_FOUND:
309                        return null;
310                    case Thumbnail.THUMBNAIL_DELETED:
311                        cancel(true);
312                        return null;
313                }
314            }
315            return t;
316        }
317
318        @Override
319        protected void onPostExecute(Thumbnail thumbnail) {
320            if (isCancelled()) return;
321            mThumbnail = thumbnail;
322            updateThumbnailView();
323        }
324    }
325
326    protected void gotoGallery() {
327        // Move the next picture with capture animation. "1" means next.
328        mAppBridge.switchWithCaptureAnimation(1);
329    }
330
331    protected void saveThumbnailToFile() {
332        if (mThumbnail != null && !mThumbnail.fromFile()) {
333            new SaveThumbnailTask().execute(mThumbnail);
334        }
335    }
336
337    private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> {
338        @Override
339        protected Void doInBackground(Thumbnail... params) {
340            final int n = params.length;
341            final File filesDir = getFilesDir();
342            for (int i = 0; i < n; i++) {
343                params[i].saveLastThumbnailToFile(filesDir);
344            }
345            return null;
346        }
347    }
348
349    // Call this after setContentView.
350    protected void createCameraScreenNail(boolean getPictures) {
351        mCameraAppView = findViewById(R.id.camera_app_root);
352        Bundle data = new Bundle();
353        String path = "/local/all/";
354        // Intent mode does not show camera roll. Use 0 as a work around for
355        // invalid bucket id.
356        // TODO: add support of empty media set in gallery.
357        path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0");
358        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
359        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
360
361        // Send an AppBridge to gallery to enable the camera preview.
362        mAppBridge = new MyAppBridge();
363        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
364        getStateManager().startState(PhotoPage.class, data);
365        mCameraScreenNail = mAppBridge.getCameraScreenNail();
366    }
367
368    private class HideCameraAppView implements Runnable {
369        @Override
370        public void run() {
371            // We cannot set this as GONE because we want to receive the
372            // onLayoutChange() callback even when we are invisible.
373            mCameraAppView.setVisibility(View.INVISIBLE);
374        }
375    }
376
377    protected void updateCameraAppView() {
378        if (mShowCameraAppView) {
379            mCameraAppView.setVisibility(View.VISIBLE);
380            // The "transparent region" is not recomputed when a sibling of
381            // SurfaceView changes visibility (unless it involves GONE). It's
382            // been broken since 1.0. Call requestLayout to work around it.
383            mCameraAppView.requestLayout();
384            // withEndAction(null) prevents the pending end action
385            // mHideCameraAppView from being executed.
386            mCameraAppView.animate()
387                    .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
388                    .withLayer().alpha(1).withEndAction(null);
389        } else {
390            mCameraAppView.animate()
391                    .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
392                    .withLayer().alpha(0).withEndAction(mHideCameraAppView);
393        }
394    }
395
396    private void onFullScreenChanged(boolean full) {
397        if (mShowCameraAppView == full) return;
398        mShowCameraAppView = full;
399        if (mPaused || isFinishing()) return;
400        // Initialize the animation.
401        if (mHideCameraAppView == null) {
402            mHideCameraAppView = new HideCameraAppView();
403            mCameraAppView.animate()
404                .setInterpolator(new DecelerateInterpolator());
405        }
406        updateCameraAppView();
407
408        // If we received DELETE_PICTURE broadcasts while the Camera UI is
409        // hidden, we update the thumbnail now.
410        if (full && mUpdateThumbnailDelayed) {
411            getLastThumbnailUncached();
412            mUpdateThumbnailDelayed = false;
413        }
414    }
415
416    @Override
417    public GalleryActionBar getGalleryActionBar() {
418        return mActionBar;
419    }
420
421    // Preview frame layout has changed.
422    @Override
423    public void onLayoutChange(View v, int left, int top, int right, int bottom,
424            int oldLeft, int oldTop, int oldRight, int oldBottom) {
425        if (mAppBridge == null) return;
426
427        if (left == oldLeft && top == oldTop && right == oldRight
428                && bottom == oldBottom) {
429            return;
430        }
431
432
433        int width = right - left;
434        int height = bottom - top;
435        if (Util.getDisplayRotation(this) % 180 == 0) {
436            mCameraScreenNail.setPreviewFrameLayoutSize(width, height);
437        } else {
438            // Swap the width and height. Camera screen nail draw() is based on
439            // natural orientation, not the view system orientation.
440            mCameraScreenNail.setPreviewFrameLayoutSize(height, width);
441        }
442
443        // Find out the coordinates of the preview frame relative to GL
444        // root view.
445        View root = (View) getGLRoot();
446        int[] rootLocation = new int[2];
447        int[] viewLocation = new int[2];
448        root.getLocationInWindow(rootLocation);
449        v.getLocationInWindow(viewLocation);
450
451        int l = viewLocation[0] - rootLocation[0];
452        int t = viewLocation[1] - rootLocation[1];
453        int r = l + width;
454        int b = t + height;
455        Rect frame = new Rect(l, t, r, b);
456        Log.d(TAG, "set CameraRelativeFrame as " + frame);
457        mAppBridge.setCameraRelativeFrame(frame);
458    }
459
460    protected void setSingleTapUpListener(View singleTapArea) {
461        mSingleTapArea = singleTapArea;
462    }
463
464    private boolean onSingleTapUp(int x, int y) {
465        // Ignore if listener is null or the camera control is invisible.
466        if (mSingleTapArea == null || !mShowCameraAppView) return false;
467
468        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
469                mSingleTapArea);
470        x -= relativeLocation[0];
471        y -= relativeLocation[1];
472        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
473                && y < mSingleTapArea.getHeight()) {
474            onSingleTapUp(mSingleTapArea, x, y);
475            return true;
476        }
477        return false;
478    }
479
480    protected void onSingleTapUp(View view, int x, int y) {
481    }
482
483    protected void setSwipingEnabled(boolean enabled) {
484        mAppBridge.setSwipingEnabled(enabled);
485    }
486
487    protected void notifyScreenNailChanged() {
488        mAppBridge.notifyScreenNailChanged();
489    }
490
491    protected void onPreviewTextureCopied() {
492    }
493
494    //////////////////////////////////////////////////////////////////////////
495    //  The is the communication interface between the Camera Application and
496    //  the Gallery PhotoPage.
497    //////////////////////////////////////////////////////////////////////////
498
499    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
500        private CameraScreenNail mCameraScreenNail;
501        private Server mServer;
502
503        @Override
504        public ScreenNail attachScreenNail() {
505            if (mCameraScreenNail == null) {
506                mCameraScreenNail = new CameraScreenNail(this);
507            }
508            return mCameraScreenNail;
509        }
510
511        @Override
512        public void detachScreenNail() {
513            mCameraScreenNail = null;
514        }
515
516        public CameraScreenNail getCameraScreenNail() {
517            return mCameraScreenNail;
518        }
519
520        // Return true if the tap is consumed.
521        @Override
522        public boolean onSingleTapUp(int x, int y) {
523            return ActivityBase.this.onSingleTapUp(x, y);
524        }
525
526        // This is used to notify that the screen nail will be drawn in full screen
527        // or not in next draw() call.
528        @Override
529        public void onFullScreenChanged(boolean full) {
530            ActivityBase.this.onFullScreenChanged(full);
531        }
532
533        @Override
534        public void requestRender() {
535            getGLRoot().requestRender();
536        }
537
538        @Override
539        public void onPreviewTextureCopied() {
540            ActivityBase.this.onPreviewTextureCopied();
541        }
542
543        @Override
544        public void setServer(Server s) {
545            mServer = s;
546        }
547
548        @Override
549        public boolean isPanorama() {
550            return ActivityBase.this.isPanoramaActivity();
551        }
552
553        private void setCameraRelativeFrame(Rect frame) {
554            if (mServer != null) mServer.setCameraRelativeFrame(frame);
555        }
556
557        private void switchWithCaptureAnimation(int offset) {
558            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
559        }
560
561        private void setSwipingEnabled(boolean enabled) {
562            if (mServer != null) mServer.setSwipingEnabled(enabled);
563        }
564
565        private void notifyScreenNailChanged() {
566            if (mServer != null) mServer.notifyScreenNailChanged();
567        }
568    }
569}
570