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