CameraActivity.java revision 9f1db5210361802a30a7866825c3b29ef5fe0024
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 onReload() {
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                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1182                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1183            moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1184        } else {
1185            // If the activity has not been started using an explicit intent,
1186            // read the module index from the last time the user changed modes
1187            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1188            moduleIndex = prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1);
1189            if ((moduleIndex == ModuleSwitcher.GCAM_MODULE_INDEX &&
1190                    !GcamHelper.hasGcamCapture()) || moduleIndex < 0) {
1191                moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1192            }
1193        }
1194
1195        mOrientationManager = new OrientationManagerImpl(this);
1196        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1197        setModuleFromIndex(moduleIndex);
1198        mCurrentModule.init(this, mCameraModuleRootView);
1199
1200        if (!mSecureCamera) {
1201            mDataAdapter = mWrappedDataAdapter;
1202            mFilmstripController.setDataAdapter(mDataAdapter);
1203            if (!isCaptureIntent()) {
1204                mDataAdapter.requestLoad(getContentResolver());
1205            }
1206        } else {
1207            // Put a lock placeholder as the last image by setting its date to
1208            // 0.
1209            ImageView v = (ImageView) getLayoutInflater().inflate(
1210                    R.layout.secure_album_placeholder, null);
1211            v.setOnClickListener(new View.OnClickListener() {
1212                @Override
1213                public void onClick(View view) {
1214                    try {
1215                        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1216                                UsageStatistics.ACTION_GALLERY, null);
1217                        startActivity(IntentHelper.getGalleryIntent(CameraActivity.this));
1218                    } catch (ActivityNotFoundException e) {
1219                        Log.w(TAG, "Failed to launch gallery activity, closing");
1220                    }
1221                    finish();
1222                }
1223            });
1224            mDataAdapter = new FixedLastDataAdapter(
1225                    mWrappedDataAdapter,
1226                    new SimpleViewData(
1227                            v,
1228                            v.getDrawable().getIntrinsicWidth(),
1229                            v.getDrawable().getIntrinsicHeight(),
1230                            0, 0));
1231            // Flush out all the original data.
1232            mDataAdapter.flush();
1233            mFilmstripController.setDataAdapter(mDataAdapter);
1234        }
1235
1236        setupNfcBeamPush();
1237
1238        mLocalImagesObserver = new LocalMediaObserver();
1239        mLocalVideosObserver = new LocalMediaObserver();
1240
1241        getContentResolver().registerContentObserver(
1242                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1243                mLocalImagesObserver);
1244        getContentResolver().registerContentObserver(
1245                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1246                mLocalVideosObserver);
1247    }
1248
1249    private void setRotationAnimation() {
1250        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1251        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1252        Window win = getWindow();
1253        WindowManager.LayoutParams winParams = win.getAttributes();
1254        winParams.rotationAnimation = rotationAnimation;
1255        win.setAttributes(winParams);
1256    }
1257
1258    @Override
1259    public void onUserInteraction() {
1260        super.onUserInteraction();
1261        mCurrentModule.onUserInteraction();
1262    }
1263
1264    @Override
1265    public boolean dispatchTouchEvent(MotionEvent ev) {
1266        boolean result = super.dispatchTouchEvent(ev);
1267        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1268            // Real deletion is postponed until the next user interaction after
1269            // the gesture that triggers deletion. Until real deletion is performed,
1270            // users can click the undo button to bring back the image that they
1271            // chose to delete.
1272            if (mPendingDeletion && !mIsUndoingDeletion) {
1273                 performDeletion();
1274            }
1275        }
1276        return result;
1277    }
1278
1279    @Override
1280    public void onPause() {
1281        // Delete photos that are pending deletion
1282        performDeletion();
1283        mCurrentModule.onPauseBeforeSuper();
1284        mOrientationManager.pause();
1285        super.onPause();
1286        mCurrentModule.onPauseAfterSuper();
1287
1288        mLocalImagesObserver.setActivityPaused(true);
1289        mLocalVideosObserver.setActivityPaused(true);
1290    }
1291
1292    @Override
1293    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1294        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1295            mResetToPreviewOnResume = false;
1296        } else {
1297            super.onActivityResult(requestCode, resultCode, data);
1298        }
1299    }
1300
1301    @Override
1302    public void onResume() {
1303        // TODO: Handle this in OrientationManager.
1304        // Auto-rotate off
1305        if (Settings.System.getInt(getContentResolver(),
1306                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1307            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1308            mAutoRotateScreen = false;
1309        } else {
1310            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1311            mAutoRotateScreen = true;
1312        }
1313
1314        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1315                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1316
1317        mOrientationManager.resume();
1318        mCurrentModule.onResumeBeforeSuper();
1319        super.onResume();
1320        mCurrentModule.onResumeAfterSuper();
1321
1322        setSwipingEnabled(true);
1323
1324        if (mResetToPreviewOnResume) {
1325            // Go to the preview on resume.
1326            mFilmstripController.goToFirstItem();
1327        }
1328        // Default is showing the preview, unless disabled by explicitly
1329        // starting an activity we want to return from to the filmstrip rather
1330        // than the preview.
1331        mResetToPreviewOnResume = true;
1332
1333        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1334                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1335            if (!mSecureCamera) {
1336                // If it's secure camera, requestLoad() should not be called
1337                // as it will load all the data.
1338                mDataAdapter.requestLoad(getContentResolver());
1339            }
1340        }
1341        mLocalImagesObserver.setActivityPaused(false);
1342        mLocalVideosObserver.setActivityPaused(false);
1343    }
1344
1345    @Override
1346    public void onStart() {
1347        super.onStart();
1348        bindMediaSaveService();
1349        mPanoramaViewHelper.onStart();
1350    }
1351
1352    @Override
1353    protected void onStop() {
1354        super.onStop();
1355        mPanoramaViewHelper.onStop();
1356        unbindMediaSaveService();
1357    }
1358
1359    @Override
1360    public void onDestroy() {
1361        if (mSecureCamera) {
1362            unregisterReceiver(mScreenOffReceiver);
1363        }
1364        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1365        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1366
1367        super.onDestroy();
1368    }
1369
1370    @Override
1371    public void onConfigurationChanged(Configuration config) {
1372        super.onConfigurationChanged(config);
1373        mCurrentModule.onConfigurationChanged(config);
1374    }
1375
1376    @Override
1377    public boolean onKeyDown(int keyCode, KeyEvent event) {
1378        if (mFilmstripController.inCameraFullscreen()) {
1379            if (mCurrentModule.onKeyDown(keyCode, event)) {
1380                return true;
1381            }
1382            // Prevent software keyboard or voice search from showing up.
1383            if (keyCode == KeyEvent.KEYCODE_SEARCH
1384                    || keyCode == KeyEvent.KEYCODE_MENU) {
1385                if (event.isLongPress()) {
1386                    return true;
1387                }
1388            }
1389        }
1390
1391        return super.onKeyDown(keyCode, event);
1392    }
1393
1394    @Override
1395    public boolean onKeyUp(int keyCode, KeyEvent event) {
1396        if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1397            return true;
1398        }
1399        return super.onKeyUp(keyCode, event);
1400    }
1401
1402    @Override
1403    public void onBackPressed() {
1404        if (!mFilmstripController.inCameraFullscreen()) {
1405            mFilmstripController.goToFirstItem();
1406        } else if (!mCurrentModule.onBackPressed()) {
1407            super.onBackPressed();
1408        }
1409    }
1410
1411    public boolean isAutoRotateScreen() {
1412        return mAutoRotateScreen;
1413    }
1414
1415    protected void updateStorageSpace() {
1416        mStorageSpaceBytes = Storage.getAvailableSpace();
1417    }
1418
1419    protected long getStorageSpaceBytes() {
1420        return mStorageSpaceBytes;
1421    }
1422
1423    protected void updateStorageSpaceAndHint() {
1424        updateStorageSpace();
1425        updateStorageHint(mStorageSpaceBytes);
1426    }
1427
1428    protected void updateStorageHint(long storageSpace) {
1429        String message = null;
1430        if (storageSpace == Storage.UNAVAILABLE) {
1431            message = getString(R.string.no_storage);
1432        } else if (storageSpace == Storage.PREPARING) {
1433            message = getString(R.string.preparing_sd);
1434        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1435            message = getString(R.string.access_sd_fail);
1436        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1437            message = getString(R.string.spaceIsLow_content);
1438        }
1439
1440        if (message != null) {
1441            if (mStorageHint == null) {
1442                mStorageHint = OnScreenHint.makeText(this, message);
1443            } else {
1444                mStorageHint.setText(message);
1445            }
1446            mStorageHint.show();
1447        } else if (mStorageHint != null) {
1448            mStorageHint.cancel();
1449            mStorageHint = null;
1450        }
1451    }
1452
1453    protected void setResultEx(int resultCode) {
1454        mResultCodeForTesting = resultCode;
1455        setResult(resultCode);
1456    }
1457
1458    protected void setResultEx(int resultCode, Intent data) {
1459        mResultCodeForTesting = resultCode;
1460        mResultDataForTesting = data;
1461        setResult(resultCode, data);
1462    }
1463
1464    public int getResultCode() {
1465        return mResultCodeForTesting;
1466    }
1467
1468    public Intent getResultData() {
1469        return mResultDataForTesting;
1470    }
1471
1472    public boolean isSecureCamera() {
1473        return mSecureCamera;
1474    }
1475
1476    @Override
1477    public void onModuleSelected(int moduleIndex) {
1478        if (mCurrentModuleIndex == moduleIndex) {
1479            return;
1480        }
1481
1482        CameraHolder.instance().keep();
1483        closeModule(mCurrentModule);
1484        setModuleFromIndex(moduleIndex);
1485
1486        openModule(mCurrentModule);
1487        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1488        if (mMediaSaver != null) {
1489            mCurrentModule.onMediaSaverAvailable(mMediaSaver);
1490        }
1491
1492        // Store the module index so we can use it the next time the Camera
1493        // starts up.
1494        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1495        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, moduleIndex).apply();
1496    }
1497
1498    /**
1499     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1500     * index an sets it as mCurrentModule.
1501     */
1502    private void setModuleFromIndex(int moduleIndex) {
1503        mCurrentModuleIndex = moduleIndex;
1504        switch (moduleIndex) {
1505            case ModuleSwitcher.VIDEO_MODULE_INDEX:
1506                mCurrentModule = new VideoModule();
1507                break;
1508
1509            case ModuleSwitcher.PHOTO_MODULE_INDEX:
1510                mCurrentModule = new PhotoModule();
1511                break;
1512
1513            case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX:
1514                mCurrentModule = new WideAnglePanoramaModule();
1515                break;
1516
1517            case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX:
1518                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
1519                break;
1520            case ModuleSwitcher.GCAM_MODULE_INDEX:
1521                // Force immediate release of Camera instance
1522                CameraHolder.instance().strongRelease();
1523                mCurrentModule = GcamHelper.createGcamModule();
1524                break;
1525            case ModuleSwitcher.REFOCUS_MODULE_INDEX:
1526                mCurrentModule = RefocusHelper.createRefocusModule();
1527                break;
1528            default:
1529                // Fall back to photo mode.
1530                mCurrentModule = new PhotoModule();
1531                mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
1532                break;
1533        }
1534    }
1535
1536    /**
1537     * Launches an ACTION_EDIT intent for the given local data item.
1538     */
1539    public void launchEditor(LocalData data) {
1540        Intent intent = new Intent(Intent.ACTION_EDIT)
1541                .setDataAndType(data.getContentUri(), data.getMimeType())
1542                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1543        try {
1544            startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1545        } catch (ActivityNotFoundException e) {
1546            startActivityForResult(Intent.createChooser(intent, null),
1547                    REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1548        }
1549    }
1550
1551    /**
1552     * Launch the tiny planet editor.
1553     *
1554     * @param data the data must be a 360 degree stereographically mapped
1555     *            panoramic image. It will not be modified, instead a new item
1556     *            with the result will be added to the filmstrip.
1557     */
1558    public void launchTinyPlanetEditor(LocalData data) {
1559        TinyPlanetFragment fragment = new TinyPlanetFragment();
1560        Bundle bundle = new Bundle();
1561        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1562        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1563        fragment.setArguments(bundle);
1564        fragment.show(getFragmentManager(), "tiny_planet");
1565    }
1566
1567    private void openModule(CameraModule module) {
1568        module.init(this, mCameraModuleRootView);
1569        module.onResumeBeforeSuper();
1570        module.onResumeAfterSuper();
1571    }
1572
1573    private void closeModule(CameraModule module) {
1574        module.onPauseBeforeSuper();
1575        module.onPauseAfterSuper();
1576        ((ViewGroup) mCameraModuleRootView).removeAllViews();
1577    }
1578
1579    private void performDeletion() {
1580        if (!mPendingDeletion) {
1581            return;
1582        }
1583        hideUndoDeletionBar(false);
1584        mDataAdapter.executeDeletion(CameraActivity.this);
1585
1586        int currentId = mFilmstripController.getCurrentId();
1587        updateActionBarMenu(currentId);
1588        mFilmStripListener.onCurrentDataCentered(currentId);
1589    }
1590
1591    public void showUndoDeletionBar() {
1592        if (mPendingDeletion) {
1593            performDeletion();
1594        }
1595        Log.v(TAG, "showing undo bar");
1596        mPendingDeletion = true;
1597        if (mUndoDeletionBar == null) {
1598            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
1599                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
1600            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1601            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1602            button.setOnClickListener(new View.OnClickListener() {
1603                @Override
1604                public void onClick(View view) {
1605                    mDataAdapter.undoDataRemoval();
1606                    hideUndoDeletionBar(true);
1607                }
1608            });
1609            // Setting undo bar clickable to avoid touch events going through
1610            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1611            mUndoDeletionBar.setClickable(true);
1612            // When there is user interaction going on with the undo button, we
1613            // do not want to hide the undo bar.
1614            button.setOnTouchListener(new View.OnTouchListener() {
1615                @Override
1616                public boolean onTouch(View v, MotionEvent event) {
1617                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1618                        mIsUndoingDeletion = true;
1619                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1620                        mIsUndoingDeletion =false;
1621                    }
1622                    return false;
1623                }
1624            });
1625        }
1626        mUndoDeletionBar.setAlpha(0f);
1627        mUndoDeletionBar.setVisibility(View.VISIBLE);
1628        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1629    }
1630
1631    private void hideUndoDeletionBar(boolean withAnimation) {
1632        Log.v(TAG, "Hiding undo deletion bar");
1633        mPendingDeletion = false;
1634        if (mUndoDeletionBar != null) {
1635            if (withAnimation) {
1636                mUndoDeletionBar.animate()
1637                        .setDuration(200)
1638                        .alpha(0f)
1639                        .setListener(new Animator.AnimatorListener() {
1640                            @Override
1641                            public void onAnimationStart(Animator animation) {
1642                                // Do nothing.
1643                            }
1644
1645                            @Override
1646                            public void onAnimationEnd(Animator animation) {
1647                                mUndoDeletionBar.setVisibility(View.GONE);
1648                            }
1649
1650                            @Override
1651                            public void onAnimationCancel(Animator animation) {
1652                                // Do nothing.
1653                            }
1654
1655                            @Override
1656                            public void onAnimationRepeat(Animator animation) {
1657                                // Do nothing.
1658                            }
1659                        })
1660                        .start();
1661            } else {
1662                mUndoDeletionBar.setVisibility(View.GONE);
1663            }
1664        }
1665    }
1666
1667    @Override
1668    public void onShowSwitcherPopup() {
1669    }
1670
1671    @Override
1672    public void onOrientationChanged(int orientation) {
1673        // We keep the last known orientation. So if the user first orient
1674        // the camera then point the camera to floor or sky, we still have
1675        // the correct orientation.
1676        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1677            return;
1678        }
1679        mLastRawOrientation = orientation;
1680        if (mCurrentModule != null) {
1681            mCurrentModule.onOrientationChanged(orientation);
1682        }
1683    }
1684
1685    /**
1686     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1687     * capture intent.
1688     *
1689     * @param enable {@code true} to enable swipe.
1690     */
1691    public void setSwipingEnabled(boolean enable) {
1692        if (isCaptureIntent()) {
1693            mCameraPreviewData.lockPreview(true);
1694        } else {
1695            mCameraPreviewData.lockPreview(!enable);
1696        }
1697    }
1698
1699
1700    /**
1701     * Check whether camera controls are visible.
1702     *
1703     * @return whether controls are visible.
1704     */
1705    private boolean arePreviewControlsVisible() {
1706        return mCurrentModule.arePreviewControlsVisible();
1707    }
1708
1709    /**
1710     * Show or hide the {@link CameraControls} using the current module's
1711     * implementation of {@link #onPreviewFocusChanged}.
1712     *
1713     * @param showControls whether to show camera controls.
1714     */
1715    private void setPreviewControlsVisibility(boolean showControls) {
1716        mCurrentModule.onPreviewFocusChanged(showControls);
1717    }
1718
1719    // Accessor methods for getting latency times used in performance testing
1720    public long getAutoFocusTime() {
1721        return (mCurrentModule instanceof PhotoModule) ?
1722                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1723    }
1724
1725    public long getShutterLag() {
1726        return (mCurrentModule instanceof PhotoModule) ?
1727                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1728    }
1729
1730    public long getShutterToPictureDisplayedTime() {
1731        return (mCurrentModule instanceof PhotoModule) ?
1732                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1733    }
1734
1735    public long getPictureDisplayedToJpegCallbackTime() {
1736        return (mCurrentModule instanceof PhotoModule) ?
1737                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1738    }
1739
1740    public long getJpegCallbackFinishTime() {
1741        return (mCurrentModule instanceof PhotoModule) ?
1742                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1743    }
1744
1745    public long getCaptureStartTime() {
1746        return (mCurrentModule instanceof PhotoModule) ?
1747                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1748    }
1749
1750    public boolean isRecording() {
1751        return (mCurrentModule instanceof VideoModule) ?
1752                ((VideoModule) mCurrentModule).isRecording() : false;
1753    }
1754
1755    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1756        return mCameraOpenCallback;
1757    }
1758
1759    // For debugging purposes only.
1760    public CameraModule getCurrentModule() {
1761        return mCurrentModule;
1762    }
1763}
1764