CameraActivity.java revision e6700b8f84a0d4047dfc9e60f06f13d842b553b9
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.annotation.TargetApi;
21import android.app.ActionBar;
22import android.app.Activity;
23import android.content.ActivityNotFoundException;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.ServiceConnection;
31import android.content.SharedPreferences;
32import android.content.pm.ActivityInfo;
33import android.content.res.Configuration;
34import android.graphics.Bitmap;
35import android.graphics.SurfaceTexture;
36import android.graphics.drawable.ColorDrawable;
37import android.net.Uri;
38import android.nfc.NfcAdapter;
39import android.nfc.NfcAdapter.CreateBeamUrisCallback;
40import android.nfc.NfcEvent;
41import android.os.AsyncTask;
42import android.os.Build;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Looper;
47import android.os.Message;
48import android.preference.PreferenceManager;
49import android.provider.MediaStore;
50import android.provider.Settings;
51import android.util.Log;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
57import android.view.MotionEvent;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.Window;
61import android.view.WindowManager;
62import android.widget.FrameLayout;
63import android.widget.ImageView;
64import android.widget.ProgressBar;
65import android.widget.ShareActionProvider;
66
67import com.android.camera.app.AppController;
68import com.android.camera.app.AppManagerFactory;
69import com.android.camera.app.CameraController;
70import com.android.camera.app.CameraManager;
71import com.android.camera.app.CameraManagerFactory;
72import com.android.camera.app.CameraProvider;
73import com.android.camera.app.ImageTaskManager;
74import com.android.camera.app.MediaSaver;
75import com.android.camera.app.ModuleManagerImpl;
76import com.android.camera.app.OrientationManager;
77import com.android.camera.app.OrientationManagerImpl;
78import com.android.camera.app.PanoramaStitchingManager;
79import com.android.camera.app.PlaceholderManager;
80import com.android.camera.crop.CropActivity;
81import com.android.camera.data.CameraDataAdapter;
82import com.android.camera.data.CameraPreviewData;
83import com.android.camera.data.FixedFirstDataAdapter;
84import com.android.camera.data.FixedLastDataAdapter;
85import com.android.camera.data.InProgressDataWrapper;
86import com.android.camera.data.LocalData;
87import com.android.camera.data.LocalDataAdapter;
88import com.android.camera.data.LocalMediaObserver;
89import com.android.camera.data.MediaDetails;
90import com.android.camera.data.SimpleViewData;
91import com.android.camera.filmstrip.FilmstripController;
92import com.android.camera.filmstrip.FilmstripImageData;
93import com.android.camera.filmstrip.FilmstripListener;
94import com.android.camera.module.ModuleController;
95import com.android.camera.module.ModulesInfo;
96import com.android.camera.tinyplanet.TinyPlanetFragment;
97import com.android.camera.ui.DetailsDialog;
98import com.android.camera.ui.FilmstripView;
99import com.android.camera.ui.ModeListView;
100import com.android.camera.util.ApiHelper;
101import com.android.camera.util.CameraUtil;
102import com.android.camera.util.GcamHelper;
103import com.android.camera.util.IntentHelper;
104import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
105import com.android.camera.util.UsageStatistics;
106import com.android.camera2.R;
107
108import java.io.File;
109
110public class CameraActivity extends Activity
111        implements AppController, ModeListView.ModeSwitchListener, CameraManager.CameraOpenCallback,
112        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
113        OrientationManager.OnOrientationChangeListener {
114
115    private static final String TAG = "CAM_Activity";
116
117    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
118            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
119    public static final String ACTION_IMAGE_CAPTURE_SECURE =
120            "android.media.action.IMAGE_CAPTURE_SECURE";
121    public static final String ACTION_TRIM_VIDEO =
122            "com.android.camera.action.TRIM";
123    public static final String MEDIA_ITEM_PATH = "media-item-path";
124
125    // The intent extra for camera from secure lock screen. True if the gallery
126    // should only show newly captured pictures. sSecureAlbumId does not
127    // increment. This is used when switching between camera, camcorder, and
128    // panorama. If the extra is not set, it is in the normal camera mode.
129    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
130
131    /**
132     * Request code from an activity we started that indicated that we do not want
133     * to reset the view to the preview in onResume.
134     */
135    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
136
137    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
138
139    private static final int HIDE_ACTION_BAR = 1;
140    private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
141
142    /**
143     * Whether onResume should reset the view to the preview.
144     */
145    private boolean mResetToPreviewOnResume = true;
146
147    // Supported operations at FilmStripView. Different data has different
148    // set of supported operations.
149    private static final int SUPPORT_DELETE = 1 << 0;
150    private static final int SUPPORT_ROTATE = 1 << 1;
151    private static final int SUPPORT_INFO = 1 << 2;
152    private static final int SUPPORT_CROP = 1 << 3;
153    private static final int SUPPORT_SETAS = 1 << 4;
154    private static final int SUPPORT_EDIT = 1 << 5;
155    private static final int SUPPORT_TRIM = 1 << 6;
156    private static final int SUPPORT_SHARE = 1 << 7;
157    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
158    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
159    private static final int SUPPORT_ALL = 0xffffffff;
160
161    /**
162     * This data adapter is used by FilmStripView.
163     */
164    private LocalDataAdapter mDataAdapter;
165    /**
166     * This data adapter represents the real local camera data.
167     */
168    private LocalDataAdapter mWrappedDataAdapter;
169
170    private PanoramaStitchingManager mPanoramaManager;
171    private PlaceholderManager mPlaceholderManager;
172    private ModeListView mModeListView;
173    private int mCurrentModuleIndex;
174    private CameraModule mCurrentModule;
175    private ModuleController mCurrentModule2;
176    private ModuleManagerImpl mModuleManager;
177    private FrameLayout mAboveFilmstripControlLayout;
178    private FrameLayout mCameraModuleRootView;
179    private FilmstripController mFilmstripController;
180    private ProgressBar mBottomProgress;
181    private View mPanoStitchingPanel;
182    private int mResultCodeForTesting;
183    private Intent mResultDataForTesting;
184    private OnScreenHint mStorageHint;
185    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
186    private boolean mAutoRotateScreen;
187    private boolean mSecureCamera;
188    // This is a hack to speed up the start of SecureCamera.
189    private static boolean sFirstStartAfterScreenOn = true;
190    private int mLastRawOrientation;
191    private OrientationManagerImpl mOrientationManager;
192    private LocationManager mLocationManager;
193    private Handler mMainHandler;
194    private PanoramaViewHelper mPanoramaViewHelper;
195    private CameraPreviewData mCameraPreviewData;
196    private ActionBar mActionBar;
197    private OnActionBarVisibilityListener mOnActionBarVisibilityListener = null;
198    private Menu mActionBarMenu;
199    private ViewGroup mUndoDeletionBar;
200    private boolean mIsUndoingDeletion = false;
201
202    private Uri[] mNfcPushUris = new Uri[1];
203
204    private ShareActionProvider mStandardShareActionProvider;
205    private Intent mStandardShareIntent;
206    private ShareActionProvider mPanoramaShareActionProvider;
207    private Intent mPanoramaShareIntent;
208    private LocalMediaObserver mLocalImagesObserver;
209    private LocalMediaObserver mLocalVideosObserver;
210
211    private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
212            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
213    private boolean mPendingDeletion = false;
214
215    private Intent mVideoShareIntent;
216    private Intent mImageShareIntent;
217
218    private CameraController mCameraController;
219
220    private MediaSaver mMediaSaver;
221    private ServiceConnection mConnection = new ServiceConnection() {
222        @Override
223        public void onServiceConnected(ComponentName className, IBinder b) {
224            mMediaSaver = ((MediaSaveService.LocalBinder) b).getService();
225            mCurrentModule.onMediaSaverAvailable(mMediaSaver);
226        }
227
228        @Override
229        public void onServiceDisconnected(ComponentName className) {
230            if (mMediaSaver != null) {
231                mMediaSaver.setQueueListener(null);
232                mMediaSaver = null;
233            }
234        }
235    };
236
237
238    // close activity when screen turns off
239    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
240        @Override
241        public void onReceive(Context context, Intent intent) {
242            finish();
243        }
244    };
245
246    private static BroadcastReceiver sScreenOffReceiver;
247
248    @Override
249    public void onCameraOpened(CameraManager.CameraProxy camera) {
250        if (!mModuleManager.getModuleAgent(mCurrentModuleIndex).requestAppForCamera()) {
251            // We shouldn't be here. Just close the camera and leave.
252            camera.release(false);
253            throw new IllegalStateException("Camera opened but the module shouldn't be " +
254                    "requesting");
255        }
256        if (mCurrentModule2 != null) {
257            mCurrentModule2.onCameraAvailable(camera);
258        }
259    }
260
261    @Override
262    public void onCameraDisabled(int cameraId) {
263        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL,
264                "security");
265
266        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
267    }
268
269    @Override
270    public void onDeviceOpenFailure(int cameraId) {
271        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
272                UsageStatistics.ACTION_OPEN_FAIL, "open");
273
274        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
275    }
276
277    @Override
278    public void onReconnectionFailure(CameraManager mgr) {
279        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
280                UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
281
282        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
283    }
284
285    private static class ScreenOffReceiver extends BroadcastReceiver {
286        @Override
287        public void onReceive(Context context, Intent intent) {
288            sFirstStartAfterScreenOn = true;
289        }
290    }
291
292    private class MainHandler extends Handler {
293        public MainHandler(Looper looper) {
294            super(looper);
295        }
296
297        @Override
298        public void handleMessage(Message msg) {
299            if (msg.what == HIDE_ACTION_BAR) {
300                removeMessages(HIDE_ACTION_BAR);
301                CameraActivity.this.setSystemBarsVisibility(false);
302            }
303        }
304    }
305
306    public interface OnActionBarVisibilityListener {
307        public void onActionBarVisibilityChanged(boolean isVisible);
308    }
309
310    public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) {
311        mOnActionBarVisibilityListener = listener;
312    }
313
314    public static boolean isFirstStartAfterScreenOn() {
315        return sFirstStartAfterScreenOn;
316    }
317
318    public static void resetFirstStartAfterScreenOn() {
319        sFirstStartAfterScreenOn = false;
320    }
321
322    private String fileNameFromDataID(int dataID) {
323        final LocalData localData = mDataAdapter.getLocalData(dataID);
324
325        File localFile = new File(localData.getPath());
326        return localFile.getName();
327    }
328
329    private FilmstripListener mFilmStripListener =
330            new FilmstripListener() {
331                @Override
332                public void onDataPromoted(int dataID) {
333                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
334                            UsageStatistics.ACTION_DELETE, "promoted", 0,
335                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
336
337                    removeData(dataID);
338                }
339
340                @Override
341                public void onDataDemoted(int dataID) {
342                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
343                            UsageStatistics.ACTION_DELETE, "demoted", 0,
344                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
345
346                    removeData(dataID);
347                }
348
349                @Override
350                public void onDataFullScreenChange(int dataID, boolean full) {
351                    boolean isCameraID = isCameraPreview(dataID);
352                    if (!isCameraID) {
353                        if (!full) {
354                            // Always show action bar in filmstrip mode
355                            CameraActivity.this.setSystemBarsVisibility(true, false);
356                        } else if (mActionBar.isShowing()) {
357                            // Hide action bar after time out in full screen mode
358                            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
359                                    SHOW_ACTION_BAR_TIMEOUT_MS);
360                        }
361                    }
362                }
363
364                /**
365                 * Check if the local data corresponding to dataID is the camera
366                 * preview.
367                 *
368                 * @param dataID the ID of the local data
369                 * @return true if the local data is not null and it is the
370                 *         camera preview.
371                 */
372                private boolean isCameraPreview(int dataID) {
373                    LocalData localData = mDataAdapter.getLocalData(dataID);
374                    if (localData == null) {
375                        Log.w(TAG, "Current data ID not found.");
376                        return false;
377                    }
378                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
379                }
380
381                @Override
382                public void onDataReloaded() {
383                    setPreviewControlsVisibility(true);
384                    CameraActivity.this.setSystemBarsVisibility(false);
385                }
386
387                @Override
388                public void onCurrentDataCentered(int dataID) {
389                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
390                        // For now, We ignore all items that are not the camera preview.
391                        return;
392                    }
393
394                    if (!arePreviewControlsVisible()) {
395                        setPreviewControlsVisibility(true);
396                        CameraActivity.this.setSystemBarsVisibility(false);
397                    }
398                }
399
400                @Override
401                public void onCurrentDataOffCentered(int dataID) {
402                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
403                        // For now, We ignore all items that are not the camera preview.
404                        return;
405                    }
406
407                    if (arePreviewControlsVisible()) {
408                        setPreviewControlsVisibility(false);
409                    }
410                }
411
412                @Override
413                public void onDataFocusChanged(final int dataID, final boolean focused) {
414                    // Delay hiding action bar if there is any user interaction
415                    if (mMainHandler.hasMessages(HIDE_ACTION_BAR)) {
416                        mMainHandler.removeMessages(HIDE_ACTION_BAR);
417                        mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR,
418                                SHOW_ACTION_BAR_TIMEOUT_MS);
419                    }
420                    // TODO: This callback is UI event callback, should always
421                    // happen on UI thread. Find the reason for this
422                    // runOnUiThread() and fix it.
423                    runOnUiThread(new Runnable() {
424                        @Override
425                        public void run() {
426                            LocalData currentData = mDataAdapter.getLocalData(dataID);
427                            if (currentData == null) {
428                                Log.w(TAG, "Current data ID not found.");
429                                hidePanoStitchingProgress();
430                                return;
431                            }
432                            boolean isCameraID = currentData.getLocalDataType() ==
433                                    LocalData.LOCAL_CAMERA_PREVIEW;
434                            if (!focused) {
435                                if (isCameraID) {
436                                    mCurrentModule.onPreviewFocusChanged(false);
437                                    CameraActivity.this.setSystemBarsVisibility(true);
438                                }
439                                hidePanoStitchingProgress();
440                            } else {
441                                if (isCameraID) {
442                                    // Don't show the action bar in Camera
443                                    // preview.
444                                    CameraActivity.this.setSystemBarsVisibility(false);
445
446                                    if (mPendingDeletion) {
447                                        performDeletion();
448                                    }
449                                } else {
450                                    updateActionBarMenu(dataID);
451                                }
452
453                                Uri contentUri = currentData.getContentUri();
454                                if (contentUri == null) {
455                                    hidePanoStitchingProgress();
456                                    return;
457                                }
458                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
459                                if (panoStitchingProgress < 0) {
460                                    hidePanoStitchingProgress();
461                                    return;
462                                }
463                                showPanoStitchingProgress();
464                                updateStitchingProgress(panoStitchingProgress);
465                            }
466                        }
467                    });
468                }
469
470                @Override
471                public void onToggleSystemDecorsVisibility(int dataID) {
472                    // If action bar is showing, hide it immediately, otherwise
473                    // show action bar and hide it later
474                    if (mActionBar.isShowing()) {
475                        CameraActivity.this.setSystemBarsVisibility(false);
476                    } else {
477                        // Don't show the action bar if that is the camera preview.
478                        boolean isCameraID = isCameraPreview(dataID);
479                        if (!isCameraID) {
480                            CameraActivity.this.setSystemBarsVisibility(true, true);
481                        }
482                    }
483                }
484
485                @Override
486                public void setSystemDecorsVisibility(boolean visible) {
487                    CameraActivity.this.setSystemBarsVisibility(visible);
488                }
489            };
490
491    public void gotoGallery() {
492        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP,
493                "thumbnailTap");
494
495        mFilmstripController.goToNextItem();
496    }
497
498    /**
499     * If {@param visible} is false, this hides the action bar and switches the system UI
500     * to lights-out mode.
501     */
502    // TODO: This should not be called outside of the activity.
503    public void setSystemBarsVisibility(boolean visible) {
504        setSystemBarsVisibility(visible, false);
505    }
506
507    /**
508     * If {@param visible} is false, this hides the action bar and switches the
509     * system UI to lights-out mode. If {@param hideLater} is true, a delayed message
510     * will be sent after a timeout to hide the action bar.
511     */
512    private void setSystemBarsVisibility(boolean visible, boolean hideLater) {
513        mMainHandler.removeMessages(HIDE_ACTION_BAR);
514
515        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
516        int newSystemUIVisibility = DEFAULT_SYSTEM_UI_VISIBILITY |
517                (visible ? View.SYSTEM_UI_FLAG_VISIBLE :
518                        View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
519        if (newSystemUIVisibility != currentSystemUIVisibility) {
520            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
521        }
522
523        boolean currentActionBarVisibility = mActionBar.isShowing();
524        if (visible != currentActionBarVisibility) {
525            if (visible) {
526                mActionBar.show();
527            } else {
528                mActionBar.hide();
529            }
530            if (mOnActionBarVisibilityListener != null) {
531                mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible);
532            }
533        }
534
535        // Now delay hiding the bars
536        if (visible && hideLater) {
537            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
538        }
539    }
540
541    private void hidePanoStitchingProgress() {
542        mPanoStitchingPanel.setVisibility(View.GONE);
543    }
544
545    private void showPanoStitchingProgress() {
546        mPanoStitchingPanel.setVisibility(View.VISIBLE);
547    }
548
549    private void updateStitchingProgress(int progress) {
550        mBottomProgress.setProgress(progress);
551    }
552
553    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
554    private void setupNfcBeamPush() {
555        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
556        if (adapter == null) {
557            return;
558        }
559
560        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
561            // Disable beaming
562            adapter.setNdefPushMessage(null, CameraActivity.this);
563            return;
564        }
565
566        adapter.setBeamPushUris(null, CameraActivity.this);
567        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
568            @Override
569            public Uri[] createBeamUris(NfcEvent event) {
570                return mNfcPushUris;
571            }
572        }, CameraActivity.this);
573    }
574
575    private void setNfcBeamPushUri(Uri uri) {
576        mNfcPushUris[0] = uri;
577    }
578
579    private void setStandardShareIntent(Uri contentUri, String mimeType) {
580        mStandardShareIntent = getShareIntentFromType(mimeType);
581        if (mStandardShareIntent != null) {
582            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
583            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
584            if (mStandardShareActionProvider != null) {
585                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
586            }
587        }
588    }
589
590    /**
591     * Get the share intent according to the mimeType
592     *
593     * @param mimeType The mimeType of current data.
594     * @return the video/image's ShareIntent or null if mimeType is invalid.
595     */
596    private Intent getShareIntentFromType(String mimeType) {
597        // Lazily create the intent object.
598        if (mimeType.startsWith("video/")) {
599            if (mVideoShareIntent == null) {
600                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
601                mVideoShareIntent.setType("video/*");
602            }
603            return mVideoShareIntent;
604        } else if (mimeType.startsWith("image/")) {
605            if (mImageShareIntent == null) {
606                mImageShareIntent = new Intent(Intent.ACTION_SEND);
607                mImageShareIntent.setType("image/*");
608            }
609            return mImageShareIntent;
610        }
611        Log.w(TAG, "unsupported mimeType " + mimeType);
612        return null;
613    }
614
615    private void setPanoramaShareIntent(Uri contentUri) {
616        if (mPanoramaShareIntent == null) {
617            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
618        }
619        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
620        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
621        if (mPanoramaShareActionProvider != null) {
622            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
623        }
624    }
625
626    @Override
627    public void onMenuVisibilityChanged(boolean isVisible) {
628        // If menu is showing, we need to make sure action bar does not go away.
629        mMainHandler.removeMessages(HIDE_ACTION_BAR);
630        if (!isVisible) {
631            mMainHandler.sendEmptyMessageDelayed(HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
632        }
633    }
634
635    @Override
636    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
637        int currentDataId = mFilmstripController.getCurrentId();
638        if (currentDataId < 0) {
639            return false;
640        }
641        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE,
642                intent.getComponent().getPackageName(), 0,
643                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
644        return true;
645    }
646
647    /**
648     * According to the data type, make the menu items for supported operations
649     * visible.
650     *
651     * @param dataID the data ID of the current item.
652     */
653    private void updateActionBarMenu(int dataID) {
654        LocalData currentData = mDataAdapter.getLocalData(dataID);
655        if (currentData == null) {
656            return;
657        }
658        int type = currentData.getLocalDataType();
659
660        if (mActionBarMenu == null) {
661            return;
662        }
663
664        int supported = 0;
665
666        switch (type) {
667            case LocalData.LOCAL_IMAGE:
668                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
669                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
670                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
671                break;
672            case LocalData.LOCAL_VIDEO:
673                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
674                        | SUPPORT_SHARE;
675                break;
676            case LocalData.LOCAL_PHOTO_SPHERE:
677                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
678                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
679                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
680                break;
681            case LocalData.LOCAL_360_PHOTO_SPHERE:
682                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
683                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
684                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
685                        | SUPPORT_SHOW_ON_MAP;
686                break;
687            default:
688                break;
689        }
690
691        // In secure camera mode, we only support delete operation.
692        if (isSecureCamera()) {
693            supported &= SUPPORT_DELETE;
694        }
695
696        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
697                (supported & SUPPORT_DELETE) != 0);
698        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
699                (supported & SUPPORT_ROTATE) != 0);
700        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
701                (supported & SUPPORT_ROTATE) != 0);
702        setMenuItemVisible(mActionBarMenu, R.id.action_details,
703                (supported & SUPPORT_INFO) != 0);
704        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
705                (supported & SUPPORT_CROP) != 0);
706        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
707                (supported & SUPPORT_SETAS) != 0);
708        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
709                (supported & SUPPORT_EDIT) != 0);
710        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
711                (supported & SUPPORT_TRIM) != 0);
712
713        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
714        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
715        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
716        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
717
718        if (panoramaShare) {
719            // For 360 PhotoSphere, relegate standard share to the overflow menu
720            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
721            if (item != null) {
722                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
723                item.setTitle(getResources().getString(R.string.share_as_photo));
724            }
725            // And, promote "share as panorama" to action bar
726            item = mActionBarMenu.findItem(R.id.action_share_panorama);
727            if (item != null) {
728                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
729            }
730            setPanoramaShareIntent(currentData.getContentUri());
731        }
732        if (standardShare) {
733            if (!panoramaShare) {
734                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
735                if (item != null) {
736                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
737                    item.setTitle(getResources().getString(R.string.share));
738                }
739            }
740            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
741            setNfcBeamPushUri(currentData.getContentUri());
742        }
743
744        boolean itemHasLocation = currentData.getLatLong() != null;
745        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
746                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
747    }
748
749    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
750        MenuItem item = menu.findItem(itemId);
751        if (item != null) {
752            item.setVisible(visible);
753        }
754    }
755
756    private ImageTaskManager.TaskListener mPlaceholderListener =
757            new ImageTaskManager.TaskListener() {
758
759                @Override
760                public void onTaskQueued(String filePath, final Uri imageUri) {
761                    mMainHandler.post(new Runnable() {
762                        @Override
763                        public void run() {
764                            notifyNewMedia(imageUri);
765                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
766                            if (dataID != -1) {
767                                LocalData d = mDataAdapter.getLocalData(dataID);
768                                InProgressDataWrapper newData = new InProgressDataWrapper(d, true);
769                                mDataAdapter.updateData(dataID, newData);
770                            }
771                        }
772                    });
773                }
774
775                @Override
776                public void onTaskDone(String filePath, final Uri imageUri) {
777                    mMainHandler.post(new Runnable() {
778                        @Override
779                        public void run() {
780                            mDataAdapter.refresh(getContentResolver(), imageUri);
781                        }
782                    });
783                }
784
785                @Override
786                public void onTaskProgress(String filePath, Uri imageUri, int progress) {
787                    // Do nothing
788                }
789            };
790
791    private ImageTaskManager.TaskListener mStitchingListener =
792            new ImageTaskManager.TaskListener() {
793                @Override
794                public void onTaskQueued(String filePath, final Uri imageUri) {
795                    mMainHandler.post(new Runnable() {
796                        @Override
797                        public void run() {
798                            notifyNewMedia(imageUri);
799                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
800                            if (dataID != -1) {
801                                // Don't allow special UI actions (swipe to
802                                // delete, for example) on in-progress data.
803                                LocalData d = mDataAdapter.getLocalData(dataID);
804                                InProgressDataWrapper newData = new InProgressDataWrapper(d);
805                                mDataAdapter.updateData(dataID, newData);
806                            }
807                        }
808                    });
809                }
810
811                @Override
812                public void onTaskDone(String filePath, final Uri imageUri) {
813                    Log.v(TAG, "onTaskDone:" + filePath);
814                    mMainHandler.post(new Runnable() {
815                        @Override
816                        public void run() {
817                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
818                            int currentDataId = mFilmstripController.getCurrentId();
819
820                            if (currentDataId == doneID) {
821                                hidePanoStitchingProgress();
822                                updateStitchingProgress(0);
823                            }
824
825                            mDataAdapter.refresh(getContentResolver(), imageUri);
826                        }
827                    });
828                }
829
830                @Override
831                public void onTaskProgress(
832                        String filePath, final Uri imageUri, final int progress) {
833                    mMainHandler.post(new Runnable() {
834                        @Override
835                        public void run() {
836                            int currentDataId = mFilmstripController.getCurrentId();
837                            if (currentDataId == -1) {
838                                return;
839                            }
840                            if (imageUri.equals(
841                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
842                                updateStitchingProgress(progress);
843                            }
844                        }
845                    });
846                }
847            };
848
849    @Override
850    public Context getAndroidContext() {
851        return this;
852    }
853
854    @Override
855    public SurfaceTexture getPreviewBuffer() {
856        // TODO: implement this
857        return null;
858    }
859
860    @Override
861    public FrameLayout getModuleLayoutRoot() {
862        return mCameraModuleRootView;
863    }
864
865    @Override
866    public void setShutterEventsListener(ShutterEventsListener listener) {
867        // TODO: implement this
868    }
869
870    @Override
871    public void setShutterEnabled(boolean enabled) {
872        // TODO: implement this
873    }
874
875    @Override
876    public boolean isShutterEnabled() {
877        // TODO: implement this
878        return false;
879    }
880
881    @Override
882    public void startPreCaptureAnimation() {
883        // TODO: implement this
884    }
885
886    @Override
887    public void cancelPreCaptureAnimation() {
888        // TODO: implement this
889    }
890
891    @Override
892    public void startPostCaptureAnimation() {
893        // TODO: implement this
894    }
895
896    @Override
897    public void startPostCaptureAnimation(Bitmap thumbnail) {
898        // TODO: implement this
899    }
900
901    @Override
902    public void cancelPostCaptureAnimation() {
903        // TODO: implement this
904    }
905
906    @Override
907    public MediaSaver getMediaSaver() {
908        return mMediaSaver;
909    }
910
911    @Override
912    public OrientationManager getOrientationManager() {
913        return mOrientationManager;
914    }
915
916    @Override
917    public LocationManager getLocationManager() {
918        return mLocationManager;
919    }
920
921    @Override
922    public void lockOrientation() {
923        mOrientationManager.lockOrientation();
924    }
925
926    @Override
927    public void unlockOrientation() {
928        mOrientationManager.unlockOrientation();
929    }
930
931    @Override
932    public void notifyNewMedia(Uri uri) {
933        ContentResolver cr = getContentResolver();
934        String mimeType = cr.getType(uri);
935        if (mimeType.startsWith("video/")) {
936            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
937            mDataAdapter.addNewVideo(cr, uri);
938        } else if (mimeType.startsWith("image/")) {
939            CameraUtil.broadcastNewPicture(this, uri);
940            mDataAdapter.addNewPhoto(cr, uri);
941        } else if (mimeType.startsWith("application/stitching-preview")) {
942            mDataAdapter.addNewPhoto(cr, uri);
943        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
944            mDataAdapter.addNewPhoto(cr, uri);
945        } else {
946            android.util.Log.w(TAG, "Unknown new media with MIME type:"
947                    + mimeType + ", uri:" + uri);
948        }
949    }
950
951    @Override
952    public CameraProvider getCameraProvider() {
953        return mCameraController;
954    }
955
956    private void removeData(int dataID) {
957        mDataAdapter.removeData(CameraActivity.this, dataID);
958        if (mDataAdapter.getTotalNumber() > 1) {
959            showUndoDeletionBar();
960        } else {
961            // If camera preview is the only view left in filmstrip,
962            // no need to show undo bar.
963            mPendingDeletion = true;
964            performDeletion();
965        }
966    }
967
968    private void bindMediaSaveService() {
969        Intent intent = new Intent(this, MediaSaveService.class);
970        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
971    }
972
973    private void unbindMediaSaveService() {
974        if (mConnection != null) {
975            unbindService(mConnection);
976        }
977    }
978
979    @Override
980    public boolean onCreateOptionsMenu(Menu menu) {
981        // Inflate the menu items for use in the action bar
982        MenuInflater inflater = getMenuInflater();
983        inflater.inflate(R.menu.operations, menu);
984        mActionBarMenu = menu;
985
986        // Configure the standard share action provider
987        MenuItem item = menu.findItem(R.id.action_share);
988        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
989        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
990        if (mStandardShareIntent != null) {
991            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
992        }
993
994        // Configure the panorama share action provider
995        item = menu.findItem(R.id.action_share_panorama);
996        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
997        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
998        if (mPanoramaShareIntent != null) {
999            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
1000        }
1001
1002        mStandardShareActionProvider.setOnShareTargetSelectedListener(this);
1003        mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this);
1004
1005        return super.onCreateOptionsMenu(menu);
1006    }
1007
1008    @Override
1009    public boolean onOptionsItemSelected(MenuItem item) {
1010        int currentDataId = mFilmstripController.getCurrentId();
1011        if (currentDataId < 0) {
1012            return false;
1013        }
1014        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
1015
1016        // Handle presses on the action bar items
1017        switch (item.getItemId()) {
1018            case android.R.id.home:
1019                // ActionBar's Up/Home button was clicked
1020                try {
1021                    startActivity(IntentHelper.getGalleryIntent(this));
1022                    return true;
1023                } catch (ActivityNotFoundException e) {
1024                    Log.w(TAG, "Failed to launch gallery activity, closing");
1025                    finish();
1026                }
1027            case R.id.action_delete:
1028                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1029                        UsageStatistics.ACTION_DELETE, null, 0,
1030                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1031                removeData(currentDataId);
1032                return true;
1033            case R.id.action_edit:
1034                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1035                        UsageStatistics.ACTION_EDIT, null, 0,
1036                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1037                launchEditor(localData);
1038                return true;
1039            case R.id.action_trim: {
1040                // This is going to be handled by the Gallery app.
1041                Intent intent = new Intent(ACTION_TRIM_VIDEO);
1042                LocalData currentData = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1043                intent.setData(currentData.getContentUri());
1044                // We need the file path to wrap this into a RandomAccessFile.
1045                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
1046                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1047                return true;
1048            }
1049            case R.id.action_rotate_ccw:
1050                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
1051                return true;
1052            case R.id.action_rotate_cw:
1053                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
1054                return true;
1055            case R.id.action_crop: {
1056                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1057                        UsageStatistics.ACTION_CROP, null, 0,
1058                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1059                Intent intent = new Intent(CropActivity.CROP_ACTION);
1060                intent.setClass(this, CropActivity.class);
1061                intent.setDataAndType(localData.getContentUri(), localData.getMimeType())
1062                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1063                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1064                return true;
1065            }
1066            case R.id.action_setas: {
1067                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
1068                        .setDataAndType(localData.getContentUri(),
1069                                localData.getMimeType())
1070                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1071                intent.putExtra("mimeType", intent.getType());
1072                startActivityForResult(Intent.createChooser(
1073                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1074                return true;
1075            }
1076            case R.id.action_details:
1077                (new AsyncTask<Void, Void, MediaDetails>() {
1078                    @Override
1079                    protected MediaDetails doInBackground(Void... params) {
1080                        return localData.getMediaDetails(CameraActivity.this);
1081                    }
1082
1083                    @Override
1084                    protected void onPostExecute(MediaDetails mediaDetails) {
1085                        if (mediaDetails != null) {
1086                            DetailsDialog.create(CameraActivity.this, mediaDetails).show();
1087                        }
1088                    }
1089                }).execute();
1090                return true;
1091            case R.id.action_show_on_map:
1092                double[] latLong = localData.getLatLong();
1093                if (latLong != null) {
1094                    CameraUtil.showOnMap(this, latLong);
1095                }
1096                return true;
1097            default:
1098                return super.onOptionsItemSelected(item);
1099        }
1100    }
1101
1102    private boolean isCaptureIntent() {
1103        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1104                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1105                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1106            return true;
1107        } else {
1108            return false;
1109        }
1110    }
1111
1112    @Override
1113    public void onCreate(Bundle state) {
1114        super.onCreate(state);
1115        GcamHelper.init(getContentResolver());
1116
1117        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1118        setContentView(R.layout.activity_main);
1119        mActionBar = getActionBar();
1120        mActionBar.addOnMenuVisibilityListener(this);
1121        mMainHandler = new MainHandler(getMainLooper());
1122        mCameraController =
1123                new CameraController(this, this, mMainHandler,
1124                        CameraManagerFactory.getAndroidCameraManager());
1125        // TODO: Try to move all the resources allocation to happen as soon as
1126        // possible so we can call module.init() at the earliest time.
1127        mModuleManager = new ModuleManagerImpl();
1128        ModulesInfo.setupModules(this, mModuleManager);
1129
1130        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1131        if (mModeListView != null) {
1132            mModeListView.setModeSwitchListener(this);
1133        } else {
1134            Log.e(TAG, "Cannot find mode list in the view hierarchy");
1135        }
1136
1137        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1138            setRotationAnimation();
1139        }
1140
1141        // Check if this is in the secure camera mode.
1142        Intent intent = getIntent();
1143        String action = intent.getAction();
1144        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1145                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1146            mSecureCamera = true;
1147        } else {
1148            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1149        }
1150
1151        if (mSecureCamera) {
1152            // Change the window flags so that secure camera can show when locked
1153            Window win = getWindow();
1154            WindowManager.LayoutParams params = win.getAttributes();
1155            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1156            win.setAttributes(params);
1157
1158            // Filter for screen off so that we can finish secure camera activity
1159            // when screen is off.
1160            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1161            registerReceiver(mScreenOffReceiver, filter);
1162            // TODO: This static screen off event receiver is a workaround to the
1163            // double onResume() invocation (onResume->onPause->onResume). We should
1164            // find a better solution to this.
1165            if (sScreenOffReceiver == null) {
1166                sScreenOffReceiver = new ScreenOffReceiver();
1167                registerReceiver(sScreenOffReceiver, filter);
1168            }
1169        }
1170        mAboveFilmstripControlLayout =
1171                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
1172        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
1173        // Hide action bar first since we are in full screen mode first, and
1174        // switch the system UI to lights-out mode.
1175        this.setSystemBarsVisibility(false);
1176        mPanoramaManager = AppManagerFactory.getInstance(this)
1177                .getPanoramaStitchingManager();
1178        mPlaceholderManager = AppManagerFactory.getInstance(this)
1179                .getGcamProcessingManager();
1180        mPanoramaManager.addTaskListener(mStitchingListener);
1181        mPlaceholderManager.addTaskListener(mPlaceholderListener);
1182        LayoutInflater inflater = getLayoutInflater();
1183        View rootLayout = inflater.inflate(R.layout.camera, null, false);
1184        mCameraModuleRootView = (FrameLayout) rootLayout.findViewById(R.id.camera_app_root);
1185        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
1186        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
1187        mCameraPreviewData = new CameraPreviewData(rootLayout,
1188                FilmstripImageData.SIZE_FULL,
1189                FilmstripImageData.SIZE_FULL);
1190        // Put a CameraPreviewData at the first position.
1191        mWrappedDataAdapter = new FixedFirstDataAdapter(
1192                new CameraDataAdapter(new ColorDrawable(
1193                        getResources().getColor(R.color.photo_placeholder))),
1194                mCameraPreviewData);
1195        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1196        mFilmstripController.setViewGap(
1197                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1198        mPanoramaViewHelper = new PanoramaViewHelper(this);
1199        mPanoramaViewHelper.onCreate();
1200        mFilmstripController.setPanoramaViewHelper(mPanoramaViewHelper);
1201        // Set up the camera preview first so the preview shows up ASAP.
1202        mFilmstripController.setListener(mFilmStripListener);
1203
1204        int moduleIndex = -1;
1205        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1206                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1207            moduleIndex = ModeListView.MODE_VIDEO;
1208        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1209                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1210                        .getAction())) {
1211            moduleIndex = ModeListView.MODE_PHOTO;
1212            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1213            if (prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1)
1214                        == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) {
1215                moduleIndex = ModeListView.MODE_GCAM;
1216            }
1217        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1218                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1219            moduleIndex = ModeListView.MODE_PHOTO;
1220        } else {
1221            // If the activity has not been started using an explicit intent,
1222            // read the module index from the last time the user changed modes
1223            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1224            moduleIndex = prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1);
1225            if ((moduleIndex == ModeListView.MODE_GCAM &&
1226                    !GcamHelper.hasGcamCapture()) || moduleIndex < 0) {
1227                moduleIndex = ModeListView.MODE_PHOTO;
1228            }
1229        }
1230
1231        mOrientationManager = new OrientationManagerImpl(this);
1232        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1233
1234        mLocationManager = new LocationManager(this,
1235            new LocationManager.Listener() {
1236                @Override
1237                public void showGpsOnScreenIndicator(boolean hasSignal) {
1238                }
1239
1240                @Override
1241                public void hideGpsOnScreenIndicator() {
1242                }
1243            });
1244
1245        setModuleFromIndex(moduleIndex);
1246        mCurrentModule.init(this, mCameraModuleRootView);
1247
1248        if (!mSecureCamera) {
1249            mDataAdapter = mWrappedDataAdapter;
1250            mFilmstripController.setDataAdapter(mDataAdapter);
1251            if (!isCaptureIntent()) {
1252                mDataAdapter.requestLoad(getContentResolver());
1253            }
1254        } else {
1255            // Put a lock placeholder as the last image by setting its date to
1256            // 0.
1257            ImageView v = (ImageView) getLayoutInflater().inflate(
1258                    R.layout.secure_album_placeholder, null);
1259            v.setOnClickListener(new View.OnClickListener() {
1260                @Override
1261                public void onClick(View view) {
1262                    try {
1263                        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1264                                UsageStatistics.ACTION_GALLERY, null);
1265                        startActivity(IntentHelper.getGalleryIntent(CameraActivity.this));
1266                    } catch (ActivityNotFoundException e) {
1267                        Log.w(TAG, "Failed to launch gallery activity, closing");
1268                    }
1269                    finish();
1270                }
1271            });
1272            mDataAdapter = new FixedLastDataAdapter(
1273                    mWrappedDataAdapter,
1274                    new SimpleViewData(
1275                            v,
1276                            v.getDrawable().getIntrinsicWidth(),
1277                            v.getDrawable().getIntrinsicHeight(),
1278                            0, 0));
1279            // Flush out all the original data.
1280            mDataAdapter.flush();
1281            mFilmstripController.setDataAdapter(mDataAdapter);
1282        }
1283
1284        setupNfcBeamPush();
1285
1286        mLocalImagesObserver = new LocalMediaObserver();
1287        mLocalVideosObserver = new LocalMediaObserver();
1288
1289        getContentResolver().registerContentObserver(
1290                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1291                mLocalImagesObserver);
1292        getContentResolver().registerContentObserver(
1293                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1294                mLocalVideosObserver);
1295    }
1296
1297    private void setRotationAnimation() {
1298        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1299        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1300        Window win = getWindow();
1301        WindowManager.LayoutParams winParams = win.getAttributes();
1302        winParams.rotationAnimation = rotationAnimation;
1303        win.setAttributes(winParams);
1304    }
1305
1306    @Override
1307    public void onUserInteraction() {
1308        super.onUserInteraction();
1309        mCurrentModule.onUserInteraction();
1310    }
1311
1312    @Override
1313    public boolean dispatchTouchEvent(MotionEvent ev) {
1314        boolean result = super.dispatchTouchEvent(ev);
1315        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1316            // Real deletion is postponed until the next user interaction after
1317            // the gesture that triggers deletion. Until real deletion is performed,
1318            // users can click the undo button to bring back the image that they
1319            // chose to delete.
1320            if (mPendingDeletion && !mIsUndoingDeletion) {
1321                performDeletion();
1322            }
1323        }
1324        return result;
1325    }
1326
1327    @Override
1328    public void onPause() {
1329        // Delete photos that are pending deletion
1330        performDeletion();
1331        // TODO: call mCurrentModule.pause() instead after all the modules
1332        // support pause().
1333        mCurrentModule.onPauseBeforeSuper();
1334        mOrientationManager.pause();
1335        // Close the camera and wait for the operation done.
1336        mCameraController.closeCamera();
1337        super.onPause();
1338        mCurrentModule.onPauseAfterSuper();
1339
1340        mLocalImagesObserver.setActivityPaused(true);
1341        mLocalVideosObserver.setActivityPaused(true);
1342    }
1343
1344    @Override
1345    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1346        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1347            mResetToPreviewOnResume = false;
1348        } else {
1349            super.onActivityResult(requestCode, resultCode, data);
1350        }
1351    }
1352
1353    @Override
1354    public void onResume() {
1355        // TODO: Handle this in OrientationManager.
1356        // Auto-rotate off
1357        if (Settings.System.getInt(getContentResolver(),
1358                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1359            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1360            mAutoRotateScreen = false;
1361        } else {
1362            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1363            mAutoRotateScreen = true;
1364        }
1365
1366        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1367                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1368
1369        mOrientationManager.resume();
1370        // TODO: call mCurrentModule.resume() instead after all the modules
1371        // support resume().
1372        mCurrentModule.onResumeBeforeSuper();
1373        super.onResume();
1374        mCurrentModule.onResumeAfterSuper();
1375
1376        setSwipingEnabled(true);
1377
1378        if (mResetToPreviewOnResume) {
1379            // Go to the preview on resume.
1380            mFilmstripController.goToFirstItem();
1381        }
1382        // Default is showing the preview, unless disabled by explicitly
1383        // starting an activity we want to return from to the filmstrip rather
1384        // than the preview.
1385        mResetToPreviewOnResume = true;
1386
1387        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1388                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1389            if (!mSecureCamera) {
1390                // If it's secure camera, requestLoad() should not be called
1391                // as it will load all the data.
1392                mDataAdapter.requestLoad(getContentResolver());
1393            }
1394        }
1395        mLocalImagesObserver.setActivityPaused(false);
1396        mLocalVideosObserver.setActivityPaused(false);
1397
1398        mModeListView.startAccordionAnimation();
1399    }
1400
1401    @Override
1402    public void onStart() {
1403        super.onStart();
1404        bindMediaSaveService();
1405        mPanoramaViewHelper.onStart();
1406    }
1407
1408    @Override
1409    protected void onStop() {
1410        mPanoramaViewHelper.onStop();
1411        unbindMediaSaveService();
1412
1413        CameraManagerFactory.recycle();
1414        super.onStop();
1415    }
1416
1417    @Override
1418    public void onDestroy() {
1419        if (mSecureCamera) {
1420            unregisterReceiver(mScreenOffReceiver);
1421        }
1422        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1423        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1424        super.onDestroy();
1425    }
1426
1427    @Override
1428    public void onConfigurationChanged(Configuration config) {
1429        super.onConfigurationChanged(config);
1430        mCurrentModule.onConfigurationChanged(config);
1431    }
1432
1433    @Override
1434    public boolean onKeyDown(int keyCode, KeyEvent event) {
1435        if (mFilmstripController.inCameraFullscreen()) {
1436            if (mCurrentModule.onKeyDown(keyCode, event)) {
1437                return true;
1438            }
1439            // Prevent software keyboard or voice search from showing up.
1440            if (keyCode == KeyEvent.KEYCODE_SEARCH
1441                    || keyCode == KeyEvent.KEYCODE_MENU) {
1442                if (event.isLongPress()) {
1443                    return true;
1444                }
1445            }
1446        }
1447
1448        return super.onKeyDown(keyCode, event);
1449    }
1450
1451    @Override
1452    public boolean onKeyUp(int keyCode, KeyEvent event) {
1453        if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1454            return true;
1455        }
1456        return super.onKeyUp(keyCode, event);
1457    }
1458
1459    @Override
1460    public void onBackPressed() {
1461        if (!mFilmstripController.inCameraFullscreen()) {
1462            mFilmstripController.goToFirstItem();
1463        } else if (!mCurrentModule.onBackPressed()) {
1464            super.onBackPressed();
1465        }
1466    }
1467
1468    public boolean isAutoRotateScreen() {
1469        return mAutoRotateScreen;
1470    }
1471
1472    protected void updateStorageSpace() {
1473        mStorageSpaceBytes = Storage.getAvailableSpace();
1474    }
1475
1476    protected long getStorageSpaceBytes() {
1477        return mStorageSpaceBytes;
1478    }
1479
1480    protected void updateStorageSpaceAndHint() {
1481        updateStorageSpace();
1482        updateStorageHint(mStorageSpaceBytes);
1483    }
1484
1485    protected void updateStorageHint(long storageSpace) {
1486        String message = null;
1487        if (storageSpace == Storage.UNAVAILABLE) {
1488            message = getString(R.string.no_storage);
1489        } else if (storageSpace == Storage.PREPARING) {
1490            message = getString(R.string.preparing_sd);
1491        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1492            message = getString(R.string.access_sd_fail);
1493        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1494            message = getString(R.string.spaceIsLow_content);
1495        }
1496
1497        if (message != null) {
1498            if (mStorageHint == null) {
1499                mStorageHint = OnScreenHint.makeText(this, message);
1500            } else {
1501                mStorageHint.setText(message);
1502            }
1503            mStorageHint.show();
1504        } else if (mStorageHint != null) {
1505            mStorageHint.cancel();
1506            mStorageHint = null;
1507        }
1508    }
1509
1510    protected void setResultEx(int resultCode) {
1511        mResultCodeForTesting = resultCode;
1512        setResult(resultCode);
1513    }
1514
1515    protected void setResultEx(int resultCode, Intent data) {
1516        mResultCodeForTesting = resultCode;
1517        mResultDataForTesting = data;
1518        setResult(resultCode, data);
1519    }
1520
1521    public int getResultCode() {
1522        return mResultCodeForTesting;
1523    }
1524
1525    public Intent getResultData() {
1526        return mResultDataForTesting;
1527    }
1528
1529    public boolean isSecureCamera() {
1530        return mSecureCamera;
1531    }
1532
1533    @Override
1534    public void onModeSelected(int moduleIndex) {
1535        if (mCurrentModuleIndex == moduleIndex) {
1536            return;
1537        }
1538
1539        closeModule(mCurrentModule);
1540        setModuleFromIndex(moduleIndex);
1541
1542        openModule(mCurrentModule);
1543        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1544        if (mMediaSaver != null) {
1545            mCurrentModule.onMediaSaverAvailable(mMediaSaver);
1546        }
1547
1548        // Store the module index so we can use it the next time the Camera
1549        // starts up.
1550        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1551        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, moduleIndex).apply();
1552    }
1553
1554    /**
1555     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1556     * index an sets it as mCurrentModule.
1557     */
1558    private void setModuleFromIndex(int moduleIndex) {
1559        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(moduleIndex);
1560        if (agent == null) {
1561            return;
1562        }
1563        if (!agent.requestAppForCamera()) {
1564            mCameraController.closeCamera();
1565        }
1566        mCurrentModuleIndex = agent.getModuleId();
1567        mCurrentModule2 = agent.createModule();
1568        mCurrentModule = (CameraModule) mCurrentModule2;
1569    }
1570
1571    /**
1572     * Launches an ACTION_EDIT intent for the given local data item.
1573     */
1574    public void launchEditor(LocalData data) {
1575        Intent intent = new Intent(Intent.ACTION_EDIT)
1576                .setDataAndType(data.getContentUri(), data.getMimeType())
1577                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1578        try {
1579            startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1580        } catch (ActivityNotFoundException e) {
1581            startActivityForResult(Intent.createChooser(intent, null),
1582                    REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1583        }
1584    }
1585
1586    /**
1587     * Launch the tiny planet editor.
1588     *
1589     * @param data The data must be a 360 degree stereographically mapped
1590     *             panoramic image. It will not be modified, instead a new item
1591     *             with the result will be added to the filmstrip.
1592     */
1593    public void launchTinyPlanetEditor(LocalData data) {
1594        TinyPlanetFragment fragment = new TinyPlanetFragment();
1595        Bundle bundle = new Bundle();
1596        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1597        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1598        fragment.setArguments(bundle);
1599        fragment.show(getFragmentManager(), "tiny_planet");
1600    }
1601
1602    private void openModule(CameraModule module) {
1603        module.init(this, mCameraModuleRootView);
1604        // TODO: call mCurrentModule.resume() instead after all the modules
1605        // support resume().
1606        module.onResumeBeforeSuper();
1607        module.onResumeAfterSuper();
1608    }
1609
1610    private void closeModule(CameraModule module) {
1611        // TODO: call mCurrentModule.pause() instead after all the modules
1612        // support pause().
1613        module.onPauseBeforeSuper();
1614        module.onPauseAfterSuper();
1615        ((ViewGroup) mCameraModuleRootView).removeAllViews();
1616    }
1617
1618    private void performDeletion() {
1619        if (!mPendingDeletion) {
1620            return;
1621        }
1622        hideUndoDeletionBar(false);
1623        mDataAdapter.executeDeletion(CameraActivity.this);
1624
1625        int currentId = mFilmstripController.getCurrentId();
1626        updateActionBarMenu(currentId);
1627        mFilmStripListener.onCurrentDataCentered(currentId);
1628    }
1629
1630    public void showUndoDeletionBar() {
1631        if (mPendingDeletion) {
1632            performDeletion();
1633        }
1634        Log.v(TAG, "showing undo bar");
1635        mPendingDeletion = true;
1636        if (mUndoDeletionBar == null) {
1637            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1638                    mAboveFilmstripControlLayout, true);
1639            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1640            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1641            button.setOnClickListener(new View.OnClickListener() {
1642                @Override
1643                public void onClick(View view) {
1644                    mDataAdapter.undoDataRemoval();
1645                    hideUndoDeletionBar(true);
1646                }
1647            });
1648            // Setting undo bar clickable to avoid touch events going through
1649            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1650            mUndoDeletionBar.setClickable(true);
1651            // When there is user interaction going on with the undo button, we
1652            // do not want to hide the undo bar.
1653            button.setOnTouchListener(new View.OnTouchListener() {
1654                @Override
1655                public boolean onTouch(View v, MotionEvent event) {
1656                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1657                        mIsUndoingDeletion = true;
1658                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1659                        mIsUndoingDeletion = false;
1660                    }
1661                    return false;
1662                }
1663            });
1664        }
1665        mUndoDeletionBar.setAlpha(0f);
1666        mUndoDeletionBar.setVisibility(View.VISIBLE);
1667        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1668    }
1669
1670    private void hideUndoDeletionBar(boolean withAnimation) {
1671        Log.v(TAG, "Hiding undo deletion bar");
1672        mPendingDeletion = false;
1673        if (mUndoDeletionBar != null) {
1674            if (withAnimation) {
1675                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1676                        .setListener(new Animator.AnimatorListener() {
1677                            @Override
1678                            public void onAnimationStart(Animator animation) {
1679                                // Do nothing.
1680                            }
1681
1682                            @Override
1683                            public void onAnimationEnd(Animator animation) {
1684                                mUndoDeletionBar.setVisibility(View.GONE);
1685                            }
1686
1687                            @Override
1688                            public void onAnimationCancel(Animator animation) {
1689                                // Do nothing.
1690                            }
1691
1692                            @Override
1693                            public void onAnimationRepeat(Animator animation) {
1694                                // Do nothing.
1695                            }
1696                        }).start();
1697            } else {
1698                mUndoDeletionBar.setVisibility(View.GONE);
1699            }
1700        }
1701    }
1702
1703    @Override
1704    public void onOrientationChanged(int orientation) {
1705        // We keep the last known orientation. So if the user first orient
1706        // the camera then point the camera to floor or sky, we still have
1707        // the correct orientation.
1708        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1709            return;
1710        }
1711        mLastRawOrientation = orientation;
1712        if (mCurrentModule != null) {
1713            mCurrentModule.onOrientationChanged(orientation);
1714        }
1715    }
1716
1717    /**
1718     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1719     * capture intent.
1720     *
1721     * @param enable {@code true} to enable swipe.
1722     */
1723    public void setSwipingEnabled(boolean enable) {
1724        if (isCaptureIntent()) {
1725            mCameraPreviewData.lockPreview(true);
1726        } else {
1727            mCameraPreviewData.lockPreview(!enable);
1728        }
1729    }
1730
1731
1732    /**
1733     * Check whether camera controls are visible.
1734     *
1735     * @return whether controls are visible.
1736     */
1737    private boolean arePreviewControlsVisible() {
1738        return mCurrentModule.arePreviewControlsVisible();
1739    }
1740
1741    /**
1742     * Show or hide the {@link CameraControls} using the current module's
1743     * implementation of {@link #onPreviewFocusChanged}.
1744     *
1745     * @param showControls whether to show camera controls.
1746     */
1747    private void setPreviewControlsVisibility(boolean showControls) {
1748        mCurrentModule.onPreviewFocusChanged(showControls);
1749    }
1750
1751    // Accessor methods for getting latency times used in performance testing
1752    public long getAutoFocusTime() {
1753        return (mCurrentModule instanceof PhotoModule) ?
1754                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1755    }
1756
1757    public long getShutterLag() {
1758        return (mCurrentModule instanceof PhotoModule) ?
1759                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1760    }
1761
1762    public long getShutterToPictureDisplayedTime() {
1763        return (mCurrentModule instanceof PhotoModule) ?
1764                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1765    }
1766
1767    public long getPictureDisplayedToJpegCallbackTime() {
1768        return (mCurrentModule instanceof PhotoModule) ?
1769                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1770    }
1771
1772    public long getJpegCallbackFinishTime() {
1773        return (mCurrentModule instanceof PhotoModule) ?
1774                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1775    }
1776
1777    public long getCaptureStartTime() {
1778        return (mCurrentModule instanceof PhotoModule) ?
1779                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1780    }
1781
1782    public boolean isRecording() {
1783        return (mCurrentModule instanceof VideoModule) ?
1784                ((VideoModule) mCurrentModule).isRecording() : false;
1785    }
1786
1787    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1788        return mCameraController;
1789    }
1790
1791    // For debugging purposes only.
1792    public CameraModule getCurrentModule() {
1793        return mCurrentModule;
1794    }
1795}
1796