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