CameraActivity.java revision f33880ba31aebb4d5e993bd8e8d7f715242fb984
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.app.AlertDialog;
24import android.app.Dialog;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.pm.ActivityInfo;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.graphics.Bitmap;
35import android.graphics.BitmapFactory;
36import android.graphics.Matrix;
37import android.graphics.Point;
38import android.graphics.SurfaceTexture;
39import android.graphics.drawable.ColorDrawable;
40import android.graphics.drawable.Drawable;
41import android.net.Uri;
42import android.nfc.NfcAdapter;
43import android.nfc.NfcAdapter.CreateBeamUrisCallback;
44import android.nfc.NfcEvent;
45import android.os.AsyncTask;
46import android.os.Build;
47import android.os.Bundle;
48import android.os.Handler;
49import android.os.HandlerThread;
50import android.os.Looper;
51import android.os.Message;
52import android.provider.MediaStore;
53import android.provider.Settings;
54import android.util.CameraPerformanceTracker;
55import android.view.ContextMenu;
56import android.view.ContextMenu.ContextMenuInfo;
57import android.view.KeyEvent;
58import android.view.Menu;
59import android.view.MenuInflater;
60import android.view.MenuItem;
61import android.view.MotionEvent;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.Window;
65import android.view.WindowManager;
66import android.widget.FrameLayout;
67import android.widget.ImageView;
68import android.widget.ShareActionProvider;
69
70import com.android.camera.app.AppController;
71import com.android.camera.app.CameraAppUI;
72import com.android.camera.app.CameraController;
73import com.android.camera.app.CameraManager;
74import com.android.camera.app.CameraManagerFactory;
75import com.android.camera.app.CameraProvider;
76import com.android.camera.app.CameraServices;
77import com.android.camera.app.LocationManager;
78import com.android.camera.app.ModuleManagerImpl;
79import com.android.camera.app.OrientationManager;
80import com.android.camera.app.OrientationManagerImpl;
81import com.android.camera.data.CameraDataAdapter;
82import com.android.camera.data.LocalDataViewType;
83import com.android.camera.data.FixedLastDataAdapter;
84import com.android.camera.data.LocalData;
85import com.android.camera.data.LocalDataAdapter;
86import com.android.camera.data.LocalDataUtil;
87import com.android.camera.data.LocalMediaData;
88import com.android.camera.data.LocalMediaObserver;
89import com.android.camera.data.LocalSessionData;
90import com.android.camera.data.MediaDetails;
91import com.android.camera.data.PanoramaMetadataLoader;
92import com.android.camera.data.RgbzMetadataLoader;
93import com.android.camera.data.SimpleViewData;
94import com.android.camera.debug.Log;
95import com.android.camera.filmstrip.FilmstripContentPanel;
96import com.android.camera.filmstrip.FilmstripController;
97import com.android.camera.hardware.HardwareSpec;
98import com.android.camera.hardware.HardwareSpecImpl;
99import com.android.camera.module.ModuleController;
100import com.android.camera.module.ModulesInfo;
101import com.android.camera.session.CaptureSession;
102import com.android.camera.session.CaptureSessionManager;
103import com.android.camera.session.CaptureSessionManager.SessionListener;
104import com.android.camera.settings.CameraSettingsActivity;
105import com.android.camera.settings.SettingsManager;
106import com.android.camera.settings.SettingsManager.SettingsCapabilities;
107import com.android.camera.settings.SettingsUtil;
108import com.android.camera.tinyplanet.TinyPlanetFragment;
109import com.android.camera.ui.AbstractTutorialOverlay;
110import com.android.camera.ui.DetailsDialog;
111import com.android.camera.ui.MainActivityLayout;
112import com.android.camera.ui.ModeListView;
113import com.android.camera.ui.ModeListView.ModeListVisibilityChangedListener;
114import com.android.camera.ui.PreviewStatusListener;
115import com.android.camera.util.ApiHelper;
116import com.android.camera.util.Callback;
117import com.android.camera.util.CameraUtil;
118import com.android.camera.util.FeedbackHelper;
119import com.android.camera.util.GalleryHelper;
120import com.android.camera.util.GcamHelper;
121import com.android.camera.util.IntentHelper;
122import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
123import com.android.camera.util.ReleaseDialogHelper;
124import com.android.camera.util.UsageStatistics;
125import com.android.camera.widget.FilmstripView;
126import com.android.camera.widget.Preloader;
127import com.android.camera2.R;
128import com.bumptech.glide.Glide;
129import com.bumptech.glide.resize.ImageManager;
130import com.google.common.logging.eventprotos;
131import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
132import com.google.common.logging.eventprotos.NavigationChange;
133
134import java.io.File;
135import java.io.FileInputStream;
136import java.io.FileNotFoundException;
137import java.lang.ref.WeakReference;
138import java.util.ArrayList;
139import java.util.List;
140import java.util.concurrent.Executors;
141
142public class CameraActivity extends Activity
143        implements AppController, CameraManager.CameraOpenCallback,
144        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
145        OrientationManager.OnOrientationChangeListener {
146
147    private static final Log.Tag TAG = new Log.Tag("CameraActivity");
148
149    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
150            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
151    public static final String ACTION_IMAGE_CAPTURE_SECURE =
152            "android.media.action.IMAGE_CAPTURE_SECURE";
153
154    // The intent extra for camera from secure lock screen. True if the gallery
155    // should only show newly captured pictures. sSecureAlbumId does not
156    // increment. This is used when switching between camera, camcorder, and
157    // panorama. If the extra is not set, it is in the normal camera mode.
158    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
159
160    /**
161     * Request code from an activity we started that indicated that we do not
162     * want to reset the view to the preview in onResume.
163     */
164    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
165
166    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
167
168    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
169    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
170    private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs.
171    /** Load metadata for 10 items ahead of our current. */
172    private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
173
174    /** Should be used wherever a context is needed. */
175    private Context mAppContext;
176
177    /**
178     * Whether onResume should reset the view to the preview.
179     */
180    private boolean mResetToPreviewOnResume = true;
181
182    /**
183     * This data adapter is used by FilmStripView.
184     */
185    private LocalDataAdapter mDataAdapter;
186
187    /**
188     * TODO: This should be moved to the app level.
189     */
190    private SettingsManager mSettingsManager;
191
192    private ModeListView mModeListView;
193    private boolean mModeListVisible = false;
194    private int mCurrentModeIndex;
195    private CameraModule mCurrentModule;
196    private ModuleManagerImpl mModuleManager;
197    private FrameLayout mAboveFilmstripControlLayout;
198    private FilmstripController mFilmstripController;
199    private boolean mFilmstripVisible;
200    /** Whether the filmstrip fully covers the preview. */
201    private boolean mFilmstripCoversPreview = false;
202    private int mResultCodeForTesting;
203    private Intent mResultDataForTesting;
204    private OnScreenHint mStorageHint;
205    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
206    private boolean mAutoRotateScreen;
207    private boolean mSecureCamera;
208    private int mLastRawOrientation;
209    private OrientationManagerImpl mOrientationManager;
210    private LocationManager mLocationManager;
211    private ButtonManager mButtonManager;
212    private Handler mMainHandler;
213    private PanoramaViewHelper mPanoramaViewHelper;
214    private ActionBar mActionBar;
215    private ViewGroup mUndoDeletionBar;
216    private boolean mIsUndoingDeletion = false;
217
218    private final Uri[] mNfcPushUris = new Uri[1];
219
220    private LocalMediaObserver mLocalImagesObserver;
221    private LocalMediaObserver mLocalVideosObserver;
222
223    private boolean mPendingDeletion = false;
224
225    private CameraController mCameraController;
226    private boolean mPaused;
227    private CameraAppUI mCameraAppUI;
228
229    private PeekAnimationHandler mPeekAnimationHandler;
230    private HandlerThread mPeekAnimationThread;
231
232    private FeedbackHelper mFeedbackHelper;
233
234    private Intent mGalleryIntent;
235    private long mOnCreateTime;
236
237    private Menu mActionBarMenu;
238    private Preloader<Integer, AsyncTask> mPreloader;
239
240    @Override
241    public CameraAppUI getCameraAppUI() {
242        return mCameraAppUI;
243    }
244
245    // close activity when screen turns off
246    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
247        @Override
248        public void onReceive(Context context, Intent intent) {
249            finish();
250        }
251    };
252
253    /**
254     * Whether the screen is kept turned on.
255     */
256    private boolean mKeepScreenOn;
257    private int mLastLayoutOrientation;
258    private final CameraAppUI.BottomPanel.Listener mMyFilmstripBottomControlListener =
259            new CameraAppUI.BottomPanel.Listener() {
260
261                /**
262                 * If the current photo is a photo sphere, this will launch the
263                 * Photo Sphere panorama viewer.
264                 */
265                @Override
266                public void onExternalViewer() {
267                    if (mPanoramaViewHelper == null) {
268                        return;
269                    }
270                    final LocalData data = getCurrentLocalData();
271                    if (data == null) {
272                        return;
273                    }
274                    final Uri contentUri = data.getUri();
275                    if (contentUri == Uri.EMPTY) {
276                        return;
277                    }
278
279                    if (PanoramaMetadataLoader.isPanoramaAndUseViewer(data)) {
280                        mPanoramaViewHelper.showPanorama(CameraActivity.this, contentUri);
281                    } else if (RgbzMetadataLoader.hasRGBZData(data)) {
282                        mPanoramaViewHelper.showRgbz(contentUri);
283                        if (mSettingsManager.getBoolean(
284                                SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
285                            mSettingsManager.setBoolean(
286                                    SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING,
287                                    false);
288                            mCameraAppUI.clearClingForViewer(
289                                    CameraAppUI.BottomPanel.VIEWER_REFOCUS);
290                        }
291                    }
292                }
293
294                @Override
295                public void onEdit() {
296                    LocalData data = getCurrentLocalData();
297                    if (data == null) {
298                        return;
299                    }
300                    launchEditor(data);
301                }
302
303                @Override
304                public void onTinyPlanet() {
305                    LocalData data = getCurrentLocalData();
306                    if (data == null) {
307                        return;
308                    }
309                    launchTinyPlanetEditor(data);
310                }
311
312                @Override
313                public void onDelete() {
314                    final int currentDataId = getCurrentDataId();
315                    UsageStatistics.instance().photoInteraction(
316                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
317                            eventprotos.CameraEvent.InteractionType.DELETE,
318                            InteractionCause.BUTTON);
319                    removeData(currentDataId);
320                }
321
322                @Override
323                public void onShare() {
324                    final LocalData data = getCurrentLocalData();
325
326                    // If applicable, show release information before this item
327                    // is shared.
328                    if (PanoramaMetadataLoader.isPanorama(data)
329                            || RgbzMetadataLoader.hasRGBZData(data)) {
330                        ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
331                                new Callback<Void>() {
332                                    @Override
333                                    public void onCallback(Void result) {
334                                        share(data);
335                                    }
336                                });
337                    } else {
338                        share(data);
339                    }
340                }
341
342                private void share(LocalData data) {
343                    Intent shareIntent = getShareIntentByData(data);
344                    if (shareIntent != null) {
345                        try {
346                            launchActivityByIntent(shareIntent);
347                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
348                        } catch (ActivityNotFoundException ex) {
349                            // Nothing.
350                        }
351                    }
352                }
353
354                private int getCurrentDataId() {
355                    return mFilmstripController.getCurrentId();
356                }
357
358                private LocalData getCurrentLocalData() {
359                    return mDataAdapter.getLocalData(getCurrentDataId());
360                }
361
362                /**
363                 * Sets up the share intent and NFC properly according to the
364                 * data.
365                 *
366                 * @param data The data to be shared.
367                 */
368                private Intent getShareIntentByData(final LocalData data) {
369                    Intent intent = null;
370                    final Uri contentUri = data.getUri();
371                    if (PanoramaMetadataLoader.isPanorama360(data) &&
372                            data.getUri() != Uri.EMPTY) {
373                        intent = new Intent(Intent.ACTION_SEND);
374                        intent.setType("application/vnd.google.panorama360+jpg");
375                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
376                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
377                        final String mimeType = data.getMimeType();
378                        intent = getShareIntentFromType(mimeType);
379                        if (intent != null) {
380                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
381                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
382                        }
383                        intent = Intent.createChooser(intent, null);
384                    }
385                    return intent;
386                }
387
388                /**
389                 * Get the share intent according to the mimeType
390                 *
391                 * @param mimeType The mimeType of current data.
392                 * @return the video/image's ShareIntent or null if mimeType is
393                 *         invalid.
394                 */
395                private Intent getShareIntentFromType(String mimeType) {
396                    // Lazily create the intent object.
397                    Intent intent = new Intent(Intent.ACTION_SEND);
398                    if (mimeType.startsWith("video/")) {
399                        intent.setType("video/*");
400                    } else {
401                        if (mimeType.startsWith("image/")) {
402                            intent.setType("image/*");
403                        } else {
404                            Log.w(TAG, "unsupported mimeType " + mimeType);
405                        }
406                    }
407                    return intent;
408                }
409
410                @Override
411                public void onProgressErrorClicked() {
412                    LocalData data = getCurrentLocalData();
413                    getServices().getCaptureSessionManager().removeErrorMessage(
414                            data.getUri());
415                    updateBottomControlsByData(data);
416                }
417            };
418
419    private ComboPreferences mPreferences;
420
421    @Override
422    public void onCameraOpened(CameraManager.CameraProxy camera) {
423        /**
424         * The current UI requires that the flash option visibility in front-facing
425         * camera be
426         *   * disabled if back facing camera supports flash
427         *   * hidden if back facing camera does not support flash
428         * We save whether back facing camera supports flash because we cannot get
429         * this in front facing camera without a camera switch.
430         *
431         * If this preference is cleared, we also need to clear the camera facing
432         * setting so we default to opening the camera in back facing camera, and
433         * can save this flash support value again.
434         */
435        if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
436            HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
437            mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
438                hardware.isFlashSupported());
439        }
440
441        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
442            // We shouldn't be here. Just close the camera and leave.
443            camera.release(false);
444            throw new IllegalStateException("Camera opened but the module shouldn't be " +
445                    "requesting");
446        }
447        if (mCurrentModule != null) {
448            SettingsCapabilities capabilities =
449                    SettingsUtil.getSettingsCapabilities(camera);
450            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
451            mCurrentModule.onCameraAvailable(camera);
452        }
453        mCameraAppUI.onChangeCamera();
454    }
455
456    @Override
457    public void onCameraDisabled(int cameraId) {
458        UsageStatistics.instance().cameraFailure(
459                eventprotos.CameraFailure.FailureReason.SECURITY);
460        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
461    }
462
463    @Override
464    public void onDeviceOpenFailure(int cameraId) {
465        UsageStatistics.instance().cameraFailure(
466                eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
467        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
468    }
469
470    @Override
471    public void onDeviceOpenedAlready(int cameraId) {
472        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
473    }
474
475    @Override
476    public void onReconnectionFailure(CameraManager mgr) {
477        UsageStatistics.instance().cameraFailure(
478                eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
479        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
480    }
481
482    private static class MainHandler extends Handler {
483        final WeakReference<CameraActivity> mActivity;
484
485        public MainHandler(CameraActivity activity, Looper looper) {
486            super(looper);
487            mActivity = new WeakReference<CameraActivity>(activity);
488        }
489
490        @Override
491        public void handleMessage(Message msg) {
492            CameraActivity activity = mActivity.get();
493            if (activity == null) {
494                return;
495            }
496            switch (msg.what) {
497
498                case MSG_CLEAR_SCREEN_ON_FLAG: {
499                    if (!activity.mPaused) {
500                        activity.getWindow().clearFlags(
501                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
502                    }
503                    break;
504                }
505            }
506        }
507    }
508
509    private String fileNameFromDataID(int dataID) {
510        final LocalData localData = mDataAdapter.getLocalData(dataID);
511
512        File localFile = new File(localData.getPath());
513        return localFile.getName();
514    }
515
516    private final FilmstripContentPanel.Listener mFilmstripListener =
517            new FilmstripContentPanel.Listener() {
518
519                @Override
520                public void onSwipeOut() {
521                    UsageStatistics.instance().changeScreen(
522                            eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
523                            eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
524                }
525
526                @Override
527                public void onSwipeOutBegin() {
528                    mActionBar.hide();
529                    mFilmstripCoversPreview = false;
530                    updatePreviewVisibility();
531                }
532
533                @Override
534                public void onFilmstripHidden() {
535                    mFilmstripVisible = false;
536                    CameraActivity.this.setFilmstripUiVisibility(false);
537                    // When the user hide the filmstrip (either swipe out or
538                    // tap on back key) we move to the first item so next time
539                    // when the user swipe in the filmstrip, the most recent
540                    // one is shown.
541                    mFilmstripController.goToFirstItem();
542                }
543
544                @Override
545                public void onFilmstripShown() {
546                    mFilmstripVisible = true;
547                    updateUiByData(mFilmstripController.getCurrentId());
548                }
549
550                @Override
551                public void onFocusedDataLongPressed(int dataId) {
552                    // Do nothing.
553                }
554
555                @Override
556                public void onFocusedDataPromoted(int dataID) {
557                    UsageStatistics.instance().photoInteraction(
558                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
559                            eventprotos.CameraEvent.InteractionType.DELETE,
560                            InteractionCause.SWIPE_UP);
561
562                    removeData(dataID);
563                }
564
565                @Override
566                public void onFocusedDataDemoted(int dataID) {
567                    UsageStatistics.instance().photoInteraction(
568                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
569                            eventprotos.CameraEvent.InteractionType.DELETE,
570                            InteractionCause.SWIPE_DOWN);
571
572                    removeData(dataID);
573                }
574
575                @Override
576                public void onEnterFullScreenUiShown(int dataId) {
577                    if (mFilmstripVisible) {
578                        CameraActivity.this.setFilmstripUiVisibility(true);
579                    }
580                }
581
582                @Override
583                public void onLeaveFullScreenUiShown(int dataId) {
584                    // Do nothing.
585                }
586
587                @Override
588                public void onEnterFullScreenUiHidden(int dataId) {
589                    if (mFilmstripVisible) {
590                        CameraActivity.this.setFilmstripUiVisibility(false);
591                    }
592                }
593
594                @Override
595                public void onLeaveFullScreenUiHidden(int dataId) {
596                    // Do nothing.
597                }
598
599                @Override
600                public void onEnterFilmstrip(int dataId) {
601                    if (mFilmstripVisible) {
602                        CameraActivity.this.setFilmstripUiVisibility(true);
603                    }
604                }
605
606                @Override
607                public void onLeaveFilmstrip(int dataId) {
608                    // Do nothing.
609                }
610
611                @Override
612                public void onDataReloaded() {
613                    if (!mFilmstripVisible) {
614                        return;
615                    }
616                    updateUiByData(mFilmstripController.getCurrentId());
617                }
618
619                @Override
620                public void onDataUpdated(int dataId) {
621                    if (!mFilmstripVisible) {
622                        return;
623                    }
624                    updateUiByData(mFilmstripController.getCurrentId());
625                }
626
627                @Override
628                public void onEnterZoomView(int dataID) {
629                    if (mFilmstripVisible) {
630                        CameraActivity.this.setFilmstripUiVisibility(false);
631                    }
632                }
633
634                @Override
635                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
636                    if (!mFilmstripVisible) {
637                        return;
638                    }
639                    // TODO: This callback is UI event callback, should always
640                    // happen on UI thread. Find the reason for this
641                    // runOnUiThread() and fix it.
642                    runOnUiThread(new Runnable() {
643                        @Override
644                        public void run() {
645                            updateUiByData(newDataId);
646                        }
647                    });
648                }
649
650                @Override
651                public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
652                    mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
653                }
654            };
655
656    private final LocalDataAdapter.LocalDataListener mLocalDataListener =
657            new LocalDataAdapter.LocalDataListener() {
658                @Override
659                public void onMetadataUpdated(List<Integer> updatedData) {
660                    int currentDataId = mFilmstripController.getCurrentId();
661                    for (Integer dataId : updatedData) {
662                        if (dataId == currentDataId) {
663                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
664                        }
665                    }
666                }
667            };
668
669    public void gotoGallery() {
670        UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
671                InteractionCause.BUTTON);
672
673        mFilmstripController.goToNextItem();
674    }
675
676    /**
677     * If 'visible' is false, this hides the action bar and switches the
678     * filmstrip UI to lights-out mode.
679     *
680     * @param visible is false, this hides the action bar and switches the
681     *            filmstrip UI to lights-out mode.
682     */
683    private void setFilmstripUiVisibility(boolean visible) {
684        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
685        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
686                : View.SYSTEM_UI_FLAG_FULLSCREEN);
687        if (newSystemUIVisibility != currentSystemUIVisibility) {
688            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
689        }
690
691        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
692        if (visible != mActionBar.isShowing()) {
693            if (visible) {
694                mActionBar.show();
695            } else {
696                mActionBar.hide();
697            }
698        }
699        mFilmstripCoversPreview = visible;
700        updatePreviewVisibility();
701    }
702
703    private void hideSessionProgress() {
704        mCameraAppUI.getFilmstripBottomControls().hideProgress();
705    }
706
707    private void showSessionProgress(CharSequence message) {
708        CameraAppUI.BottomPanel controls =  mCameraAppUI.getFilmstripBottomControls();
709        controls.setProgressText(message);
710        controls.hideControls();
711        controls.hideProgressError();
712        controls.showProgress();
713    }
714
715    private void showProcessError(CharSequence message) {
716        mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
717    }
718
719    private void updateSessionProgress(int progress) {
720        mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
721    }
722
723    private void updateSessionProgressText(CharSequence message) {
724        mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
725    }
726
727    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
728    private void setupNfcBeamPush() {
729        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
730        if (adapter == null) {
731            return;
732        }
733
734        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
735            // Disable beaming
736            adapter.setNdefPushMessage(null, CameraActivity.this);
737            return;
738        }
739
740        adapter.setBeamPushUris(null, CameraActivity.this);
741        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
742            @Override
743            public Uri[] createBeamUris(NfcEvent event) {
744                return mNfcPushUris;
745            }
746        }, CameraActivity.this);
747    }
748
749    @Override
750    public void onMenuVisibilityChanged(boolean isVisible) {
751        // TODO: Remove this or bring back the original implementation: cancel
752        // auto-hide actionbar.
753    }
754
755    @Override
756    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
757        int currentDataId = mFilmstripController.getCurrentId();
758        if (currentDataId < 0) {
759            return false;
760        }
761        UsageStatistics.instance().photoInteraction(
762                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
763                eventprotos.CameraEvent.InteractionType.SHARE,
764                InteractionCause.BUTTON);
765        // TODO add intent.getComponent().getPackageName()
766        return true;
767    }
768
769    // Note: All callbacks come back on the main thread.
770    private final SessionListener mSessionListener =
771            new SessionListener() {
772                @Override
773                public void onSessionQueued(final Uri uri) {
774                    if (!Storage.isSessionUri(uri)) {
775                        return;
776                    }
777                    LocalSessionData newData = new LocalSessionData(uri);
778                    mDataAdapter.addData(newData);
779                }
780
781                @Override
782                public void onSessionDone(final Uri sessionUri) {
783                    Log.v(TAG, "onSessionDone:" + sessionUri);
784                    Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
785                    if (contentUri == null) {
786                        mDataAdapter.refresh(sessionUri);
787                        return;
788                    }
789                    LocalData newData = LocalMediaData.PhotoData.fromContentUri(
790                            getContentResolver(), contentUri);
791
792                    final int pos = mDataAdapter.findDataByContentUri(sessionUri);
793                    if (pos == -1) {
794                        // We do not have a placeholder for this image, perhaps due to the
795                        // activity crashing or being killed.
796                        mDataAdapter.addData(newData);
797                    }  else  {
798                        mDataAdapter.updateData(pos, newData);
799                    }
800                }
801
802                @Override
803                public void onSessionProgress(final Uri uri, final int progress) {
804                    if (progress < 0) {
805                        // Do nothing, there is no task for this URI.
806                        return;
807                    }
808                    int currentDataId = mFilmstripController.getCurrentId();
809                    if (currentDataId == -1) {
810                        return;
811                    }
812                    if (uri.equals(
813                            mDataAdapter.getLocalData(currentDataId).getUri())) {
814                        updateSessionProgress(progress);
815                    }
816                }
817
818                @Override
819                public void onSessionProgressText(final Uri uri, final CharSequence message) {
820                    int currentDataId = mFilmstripController.getCurrentId();
821                    if (currentDataId == -1) {
822                        return;
823                    }
824                    if (uri.equals(
825                            mDataAdapter.getLocalData(currentDataId).getUri())) {
826                        updateSessionProgressText(message);
827                    }
828                }
829
830                @Override
831                public void onSessionUpdated(Uri uri) {
832                    mDataAdapter.refresh(uri);
833                }
834
835                @Override
836                public void onSessionPreviewAvailable(Uri uri) {
837                    mDataAdapter.refresh(uri);
838                    int dataId = mDataAdapter.findDataByContentUri(uri);
839                    if (dataId != -1) {
840                        startPeekAnimation(mDataAdapter.getLocalData(dataId));
841                    }
842                }
843
844                @Override
845                public void onSessionFailed(Uri uri, CharSequence reason) {
846                    Log.v(TAG, "onSessionFailed:" + uri);
847
848                    int failedDataId = mDataAdapter.findDataByContentUri(uri);
849                    int currentDataId = mFilmstripController.getCurrentId();
850
851                    if (currentDataId == failedDataId) {
852                        updateSessionProgress(0);
853                        showProcessError(reason);
854                    }
855                    // HERE
856                    mDataAdapter.refresh(uri);
857                }
858            };
859
860    @Override
861    public Context getAndroidContext() {
862        return mAppContext;
863    }
864
865    @Override
866    public void launchActivityByIntent(Intent intent) {
867        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
868    }
869
870    @Override
871    public int getCurrentModuleIndex() {
872        return mCurrentModeIndex;
873    }
874
875    @Override
876    public ModuleController getCurrentModuleController() {
877        return mCurrentModule;
878    }
879
880    @Override
881    public int getQuickSwitchToModuleId(int currentModuleIndex) {
882        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
883                mAppContext);
884    }
885
886    @Override
887    public SurfaceTexture getPreviewBuffer() {
888        // TODO: implement this
889        return null;
890    }
891
892    @Override
893    public void onPreviewReadyToStart() {
894        mCameraAppUI.onPreviewReadyToStart();
895    }
896
897    @Override
898    public void onPreviewStarted() {
899        mCameraAppUI.onPreviewStarted();
900    }
901
902    @Override
903    public void addPreviewAreaSizeChangedListener(
904            PreviewStatusListener.PreviewAreaChangedListener listener) {
905        mCameraAppUI.addPreviewAreaChangedListener(listener);
906    }
907
908    @Override
909    public void removePreviewAreaSizeChangedListener(
910            PreviewStatusListener.PreviewAreaChangedListener listener) {
911        mCameraAppUI.removePreviewAreaChangedListener(listener);
912    }
913
914    @Override
915    public void setupOneShotPreviewListener() {
916        mCameraController.setOneShotPreviewCallback(mMainHandler,
917                new CameraManager.CameraPreviewDataCallback() {
918                    @Override
919                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
920                        mCurrentModule.onPreviewInitialDataReceived();
921                        mCameraAppUI.onNewPreviewFrame();
922                    }
923                }
924        );
925    }
926
927    @Override
928    public void updatePreviewAspectRatio(float aspectRatio) {
929        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
930    }
931
932    @Override
933    public void updatePreviewTransform(Matrix matrix) {
934        mCameraAppUI.updatePreviewTransform(matrix);
935    }
936
937    @Override
938    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
939        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
940    }
941
942    @Override
943    public FrameLayout getModuleLayoutRoot() {
944        return mCameraAppUI.getModuleRootView();
945    }
946
947    @Override
948    public void setShutterEventsListener(ShutterEventsListener listener) {
949        // TODO: implement this
950    }
951
952    @Override
953    public void setShutterEnabled(boolean enabled) {
954        // TODO: implement this
955    }
956
957    @Override
958    public boolean isShutterEnabled() {
959        // TODO: implement this
960        return false;
961    }
962
963    @Override
964    public void startPreCaptureAnimation() {
965        mCameraAppUI.startPreCaptureAnimation();
966    }
967
968    @Override
969    public void cancelPreCaptureAnimation() {
970        // TODO: implement this
971    }
972
973    @Override
974    public void startPostCaptureAnimation() {
975        // TODO: implement this
976    }
977
978    @Override
979    public void startPostCaptureAnimation(Bitmap thumbnail) {
980        // TODO: implement this
981    }
982
983    @Override
984    public void cancelPostCaptureAnimation() {
985        // TODO: implement this
986    }
987
988    @Override
989    public OrientationManager getOrientationManager() {
990        return mOrientationManager;
991    }
992
993    @Override
994    public LocationManager getLocationManager() {
995        return mLocationManager;
996    }
997
998    @Override
999    public void lockOrientation() {
1000        if (mOrientationManager != null) {
1001            mOrientationManager.lockOrientation();
1002        }
1003    }
1004
1005    @Override
1006    public void unlockOrientation() {
1007        if (mOrientationManager != null) {
1008            mOrientationManager.unlockOrientation();
1009        }
1010    }
1011
1012    /**
1013     * Starts the filmstrip peek animation if the filmstrip is not visible.
1014     * Only {@link LocalData#LOCAL_IMAGE}, {@link
1015     * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
1016     * LocalData#LOCAL_VIDEO} are supported.
1017     *
1018     * @param data The data to peek.
1019     */
1020    private void startPeekAnimation(final LocalData data) {
1021        if (mFilmstripVisible || mPeekAnimationHandler == null) {
1022            return;
1023        }
1024
1025        int dataType = data.getLocalDataType();
1026        if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
1027                dataType != LocalData.LOCAL_VIDEO) {
1028            return;
1029        }
1030
1031        mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1032            @Override
1033            public void onCallback(Bitmap result) {
1034                mCameraAppUI.startPeekAnimation(result, true);
1035            }
1036        });
1037    }
1038
1039    @Override
1040    public void notifyNewMedia(Uri uri) {
1041        ContentResolver cr = getContentResolver();
1042        String mimeType = cr.getType(uri);
1043        if (LocalDataUtil.isMimeTypeVideo(mimeType)) {
1044            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
1045            LocalData newData = LocalMediaData.VideoData.fromContentUri(getContentResolver(), uri);
1046            if (newData == null) {
1047                Log.e(TAG, "Can't find video data in content resolver:" + uri);
1048                return;
1049            }
1050            if (mDataAdapter.addData(newData)) {
1051                startPeekAnimation(newData);
1052            }
1053        } else if (LocalDataUtil.isMimeTypeImage(mimeType)) {
1054            CameraUtil.broadcastNewPicture(mAppContext, uri);
1055            LocalData newData = LocalMediaData.PhotoData.fromContentUri(getContentResolver(), uri);
1056            if (newData == null) {
1057                Log.e(TAG, "Can't find photo data in content resolver:" + uri);
1058                return;
1059            }
1060            if (mDataAdapter.addData(newData)) {
1061                startPeekAnimation(newData);
1062            }
1063        } else {
1064            Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
1065        }
1066    }
1067
1068    @Override
1069    public void enableKeepScreenOn(boolean enabled) {
1070        if (mPaused) {
1071            return;
1072        }
1073
1074        mKeepScreenOn = enabled;
1075        if (mKeepScreenOn) {
1076            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1077            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1078        } else {
1079            keepScreenOnForAWhile();
1080        }
1081    }
1082
1083    @Override
1084    public CameraProvider getCameraProvider() {
1085        return mCameraController;
1086    }
1087
1088    private void removeData(int dataID) {
1089        mDataAdapter.removeData(dataID);
1090        if (mDataAdapter.getTotalNumber() > 1) {
1091            showUndoDeletionBar();
1092        } else {
1093            // If camera preview is the only view left in filmstrip,
1094            // no need to show undo bar.
1095            mPendingDeletion = true;
1096            performDeletion();
1097            if (mFilmstripVisible) {
1098                mCameraAppUI.getFilmstripContentPanel().animateHide();
1099            }
1100        }
1101    }
1102
1103    @Override
1104    public boolean onOptionsItemSelected(MenuItem item) {
1105        // Handle presses on the action bar items
1106        switch (item.getItemId()) {
1107            case android.R.id.home:
1108                if (mFilmstripVisible && startGallery()) {
1109                    return true;
1110                }
1111                onBackPressed();
1112                return true;
1113            case R.id.action_details:
1114                showDetailsDialog(mFilmstripController.getCurrentId());
1115                return true;
1116            default:
1117                return super.onOptionsItemSelected(item);
1118        }
1119    }
1120
1121    private boolean isCaptureIntent() {
1122        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1123                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1124                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1125            return true;
1126        } else {
1127            return false;
1128        }
1129    }
1130
1131    private final SettingsManager.StrictUpgradeCallback mStrictUpgradeCallback
1132        = new SettingsManager.StrictUpgradeCallback() {
1133                @Override
1134                public void upgrade(SettingsManager settingsManager, int version) {
1135                    // Show the location dialog on upgrade if
1136                    //  (a) the user has never set this option (status quo).
1137                    //  (b) the user opt'ed out previously.
1138                    if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION)) {
1139                        // Location is set in the source file defined for this setting.
1140                        // Remove the setting if the value is false to launch the dialog.
1141                        if (!settingsManager.getBoolean(SettingsManager.SETTING_RECORD_LOCATION)) {
1142                            settingsManager.remove(SettingsManager.SETTING_RECORD_LOCATION);
1143                        }
1144                    } else {
1145                        // Location is not set, check to see if we're upgrading from
1146                        // a different source file.
1147                        if (settingsManager.isSet(SettingsManager.SETTING_RECORD_LOCATION,
1148                                                  SettingsManager.SOURCE_GLOBAL)) {
1149                            boolean location = settingsManager.getBoolean(
1150                                SettingsManager.SETTING_RECORD_LOCATION,
1151                                SettingsManager.SOURCE_GLOBAL);
1152                            if (location) {
1153                                // Set the old setting only if the value is true, to prevent
1154                                // launching the dialog.
1155                                settingsManager.setBoolean(
1156                                    SettingsManager.SETTING_RECORD_LOCATION, location);
1157                            }
1158                        }
1159                    }
1160
1161                    settingsManager.remove(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1162                }
1163            };
1164
1165    private final CameraManager.CameraExceptionCallback mCameraDefaultExceptionCallback
1166        = new CameraManager.CameraExceptionCallback() {
1167                @Override
1168                public void onCameraException(RuntimeException e) {
1169                    Log.d(TAG, "Camera Exception", e);
1170                    CameraUtil.showErrorAndFinish(CameraActivity.this,
1171                            R.string.cannot_connect_camera);
1172                }
1173            };
1174
1175    @Override
1176    public void onCreate(Bundle state) {
1177        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
1178
1179        super.onCreate(state);
1180        final Glide glide = Glide.get();
1181        if (!glide.isImageManagerSet()) {
1182            // We load exclusively large images, so we want fewer threads to minimize jank.
1183            glide.setImageManager(new ImageManager.Builder(getApplicationContext())
1184                    .setResizeService(Executors.newSingleThreadExecutor()));
1185        }
1186
1187        mOnCreateTime = System.currentTimeMillis();
1188        mAppContext = getApplicationContext();
1189        GcamHelper.init(getContentResolver());
1190
1191        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1192        setContentView(R.layout.activity_main);
1193        mActionBar = getActionBar();
1194        mActionBar.addOnMenuVisibilityListener(this);
1195        mMainHandler = new MainHandler(this, getMainLooper());
1196        mCameraController =
1197                new CameraController(mAppContext, this, mMainHandler,
1198                        CameraManagerFactory.getAndroidCameraManager());
1199        mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback,
1200                mMainHandler);
1201
1202        mPreferences = new ComboPreferences(mAppContext);
1203
1204        mSettingsManager = new SettingsManager(mAppContext, this,
1205                mCameraController.getNumberOfCameras(), mStrictUpgradeCallback);
1206
1207        // Remove this after we get rid of ComboPreferences.
1208        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1209        mPreferences.setLocalId(mAppContext, cameraId);
1210        CameraSettings.upgradeGlobalPreferences(mPreferences,
1211                mCameraController.getNumberOfCameras());
1212        // TODO: Try to move all the resources allocation to happen as soon as
1213        // possible so we can call module.init() at the earliest time.
1214        mModuleManager = new ModuleManagerImpl();
1215        ModulesInfo.setupModules(mAppContext, mModuleManager);
1216
1217        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1218        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1219        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1220            setRotationAnimation();
1221        }
1222        mModeListView.setVisibilityChangedListener(new ModeListVisibilityChangedListener() {
1223            @Override
1224            public void onVisibilityChanged(boolean visible) {
1225                mModeListVisible = visible;
1226                updatePreviewVisibility();
1227            }
1228        });
1229
1230        // Check if this is in the secure camera mode.
1231        Intent intent = getIntent();
1232        String action = intent.getAction();
1233        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1234                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1235            mSecureCamera = true;
1236        } else {
1237            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1238        }
1239
1240        if (mSecureCamera) {
1241            // Foreground event caused by lock screen startup.
1242            // It is necessary to log this in onCreate, to avoid the
1243            // onResume->onPause->onResume sequence.
1244            UsageStatistics.instance().foregrounded(
1245                    eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1246
1247            // Change the window flags so that secure camera can show when
1248            // locked
1249            Window win = getWindow();
1250            WindowManager.LayoutParams params = win.getAttributes();
1251            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1252            win.setAttributes(params);
1253
1254            // Filter for screen off so that we can finish secure camera
1255            // activity
1256            // when screen is off.
1257            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1258            registerReceiver(mScreenOffReceiver, filter);
1259        }
1260        mCameraAppUI = new CameraAppUI(this,
1261                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1262
1263        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1264
1265        mAboveFilmstripControlLayout =
1266                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1267
1268        // Add the session listener so we can track the session progress
1269        // updates.
1270        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1271        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1272        mFilmstripController.setImageGap(
1273                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1274        mPanoramaViewHelper = new PanoramaViewHelper(this);
1275        mPanoramaViewHelper.onCreate();
1276        // Set up the camera preview first so the preview shows up ASAP.
1277        mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
1278        mDataAdapter.setLocalDataListener(mLocalDataListener);
1279
1280        mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
1281                mDataAdapter);
1282
1283        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1284        if (mSettingsManager.getBoolean(SettingsManager.SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
1285            mCameraAppUI.setupClingForViewer(CameraAppUI.BottomPanel.VIEWER_REFOCUS);
1286        }
1287
1288        mLocationManager = new LocationManager(mAppContext);
1289
1290        mOrientationManager = new OrientationManagerImpl(this);
1291        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1292
1293        setModuleFromModeIndex(getModeIndex());
1294        mCameraAppUI.prepareModuleUI();
1295        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1296
1297        if (!mSecureCamera) {
1298            mFilmstripController.setDataAdapter(mDataAdapter);
1299            if (!isCaptureIntent()) {
1300                mDataAdapter.requestLoad(new Callback<Void>() {
1301                    @Override
1302                    public void onCallback(Void result) {
1303                        fillTemporarySessions();
1304                    }
1305                });
1306            }
1307        } else {
1308            // Put a lock placeholder as the last image by setting its date to
1309            // 0.
1310            ImageView v = (ImageView) getLayoutInflater().inflate(
1311                    R.layout.secure_album_placeholder, null);
1312            v.setOnClickListener(new View.OnClickListener() {
1313                @Override
1314                public void onClick(View view) {
1315                    UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
1316                            InteractionCause.BUTTON);
1317                    startGallery();
1318                    finish();
1319                }
1320            });
1321            v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
1322            mDataAdapter = new FixedLastDataAdapter(
1323                    mAppContext,
1324                    mDataAdapter,
1325                    new SimpleViewData(
1326                            v,
1327                            LocalDataViewType.SECURE_ALBUM_PLACEHOLDER,
1328                            v.getDrawable().getIntrinsicWidth(),
1329                            v.getDrawable().getIntrinsicHeight(),
1330                            0, 0));
1331            // Flush out all the original data.
1332            mDataAdapter.flush();
1333            mFilmstripController.setDataAdapter(mDataAdapter);
1334        }
1335
1336        setupNfcBeamPush();
1337
1338        mLocalImagesObserver = new LocalMediaObserver();
1339        mLocalVideosObserver = new LocalMediaObserver();
1340
1341        getContentResolver().registerContentObserver(
1342                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1343                mLocalImagesObserver);
1344        getContentResolver().registerContentObserver(
1345                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1346                mLocalVideosObserver);
1347        if (FeedbackHelper.feedbackAvailable()) {
1348            mFeedbackHelper = new FeedbackHelper(mAppContext);
1349        }
1350    }
1351
1352    /**
1353     * Get the current mode index from the Intent or from persistent
1354     * settings.
1355     */
1356    public int getModeIndex() {
1357        int modeIndex = -1;
1358        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1359        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1360        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1361        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1362                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1363            modeIndex = videoIndex;
1364        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1365                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())) {
1366            // TODO: synchronize mode options with photo module without losing
1367            // HDR+ preferences.
1368            modeIndex = photoIndex;
1369        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1370                        .getAction())
1371                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1372            modeIndex = mSettingsManager.getInt(
1373                SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX);
1374        } else {
1375            // If the activity has not been started using an explicit intent,
1376            // read the module index from the last time the user changed modes
1377            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1378            if ((modeIndex == gcamIndex &&
1379                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1380                modeIndex = photoIndex;
1381            }
1382        }
1383        return modeIndex;
1384    }
1385
1386    /**
1387     * Call this whenever the mode drawer or filmstrip change the visibility
1388     * state.
1389     */
1390    private void updatePreviewVisibility() {
1391        if (mCurrentModule == null) {
1392            return;
1393        }
1394
1395        int visibility = getPreviewVisibility();
1396        updatePreviewRendering(visibility);
1397        updateCaptureControls(visibility);
1398        mCurrentModule.onPreviewVisibilityChanged(visibility);
1399    }
1400
1401    private void updateCaptureControls(int visibility) {
1402        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1403            mCameraAppUI.setIndicatorBottomBarWrapperVisible(false);
1404        } else {
1405            mCameraAppUI.setIndicatorBottomBarWrapperVisible(true);
1406        }
1407    }
1408
1409    private void updatePreviewRendering(int visibility) {
1410        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1411            mCameraAppUI.pausePreviewRendering();
1412        } else {
1413            mCameraAppUI.resumePreviewRendering();
1414        }
1415    }
1416
1417    private int getPreviewVisibility() {
1418        if (mFilmstripCoversPreview) {
1419            return ModuleController.VISIBILITY_HIDDEN;
1420        } else if (mModeListVisible){
1421            return ModuleController.VISIBILITY_COVERED;
1422        } else {
1423            return ModuleController.VISIBILITY_VISIBLE;
1424        }
1425    }
1426
1427    private void setRotationAnimation() {
1428        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1429        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1430        Window win = getWindow();
1431        WindowManager.LayoutParams winParams = win.getAttributes();
1432        winParams.rotationAnimation = rotationAnimation;
1433        win.setAttributes(winParams);
1434    }
1435
1436    @Override
1437    public void onUserInteraction() {
1438        super.onUserInteraction();
1439        if (!isFinishing()) {
1440            keepScreenOnForAWhile();
1441        }
1442    }
1443
1444    @Override
1445    public boolean dispatchTouchEvent(MotionEvent ev) {
1446        boolean result = super.dispatchTouchEvent(ev);
1447        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1448            // Real deletion is postponed until the next user interaction after
1449            // the gesture that triggers deletion. Until real deletion is
1450            // performed, users can click the undo button to bring back the
1451            // image that they chose to delete.
1452            if (mPendingDeletion && !mIsUndoingDeletion) {
1453                performDeletion();
1454            }
1455        }
1456        return result;
1457    }
1458
1459    @Override
1460    public void onPause() {
1461        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1462
1463        /*
1464         * Save the last module index after all secure camera and icon launches,
1465         * not just on mode switches.
1466         *
1467         * Right now we exclude capture intents from this logic, because we also
1468         * ignore the cross-Activity recovery logic in onStart for capture intents.
1469         */
1470        if (!isCaptureIntent()) {
1471            mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX,
1472                mCurrentModeIndex);
1473        }
1474
1475        mPaused = true;
1476        mPeekAnimationHandler = null;
1477        mPeekAnimationThread.quitSafely();
1478        mPeekAnimationThread = null;
1479
1480        // Delete photos that are pending deletion
1481        performDeletion();
1482        mCurrentModule.pause();
1483        mOrientationManager.pause();
1484        // Close the camera and wait for the operation done.
1485        mCameraController.closeCamera();
1486        mPanoramaViewHelper.onPause();
1487
1488        mLocalImagesObserver.setForegroundChangeListener(null);
1489        mLocalImagesObserver.setActivityPaused(true);
1490        mLocalVideosObserver.setActivityPaused(true);
1491        mPreloader.cancelAllLoads();
1492        resetScreenOn();
1493        super.onPause();
1494    }
1495
1496    @Override
1497    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1498        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1499            mResetToPreviewOnResume = false;
1500        } else {
1501            super.onActivityResult(requestCode, resultCode, data);
1502        }
1503    }
1504
1505    @Override
1506    public void onResume() {
1507        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1508
1509        mPaused = false;
1510
1511        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1512
1513        // TODO: Handle this in OrientationManager.
1514        // Auto-rotate off
1515        if (Settings.System.getInt(getContentResolver(),
1516                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1517            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1518            mAutoRotateScreen = false;
1519        } else {
1520            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1521            mAutoRotateScreen = true;
1522        }
1523
1524        if (isCaptureIntent()) {
1525            // Foreground event caused by photo or video capure intent.
1526            UsageStatistics.instance().foregrounded(
1527                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1528        } else if (!mSecureCamera) {
1529            // Foreground event that is not caused by an intent.
1530            UsageStatistics.instance().foregrounded(
1531                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1532        }
1533
1534        Drawable galleryLogo;
1535        if (mSecureCamera) {
1536            mGalleryIntent = null;
1537            galleryLogo = null;
1538        } else {
1539            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1540            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1541        }
1542        if (galleryLogo == null) {
1543            try {
1544                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1545            } catch (PackageManager.NameNotFoundException e) {
1546                Log.e(TAG, "Can't get the activity logo");
1547            }
1548        }
1549        if (mGalleryIntent != null) {
1550            mActionBar.setDisplayUseLogoEnabled(true);
1551        }
1552        mActionBar.setLogo(galleryLogo);
1553        mOrientationManager.resume();
1554        super.onResume();
1555        mPeekAnimationThread = new HandlerThread("Peek animation");
1556        mPeekAnimationThread.start();
1557        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1558
1559        mCurrentModule.hardResetSettings(mSettingsManager);
1560        mCurrentModule.resume();
1561        setSwipingEnabled(true);
1562
1563        if (!mResetToPreviewOnResume) {
1564            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1565            if (data != null) {
1566                mDataAdapter.refresh(data.getUri());
1567            }
1568        }
1569        // The share button might be disabled to avoid double tapping.
1570        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1571        // Default is showing the preview, unless disabled by explicitly
1572        // starting an activity we want to return from to the filmstrip rather
1573        // than the preview.
1574        mResetToPreviewOnResume = true;
1575
1576        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1577                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1578            if (!mSecureCamera) {
1579                // If it's secure camera, requestLoad() should not be called
1580                // as it will load all the data.
1581                if (!mFilmstripVisible) {
1582                    mDataAdapter.requestLoad(new Callback<Void>() {
1583                        @Override
1584                        public void onCallback(Void result) {
1585                            fillTemporarySessions();
1586                        }
1587                    });
1588                } else {
1589                    mDataAdapter.requestLoadNewPhotos();
1590                }
1591            }
1592        }
1593        mLocalImagesObserver.setActivityPaused(false);
1594        mLocalVideosObserver.setActivityPaused(false);
1595        if (!mSecureCamera) {
1596            mLocalImagesObserver.setForegroundChangeListener(
1597                    new LocalMediaObserver.ChangeListener() {
1598                @Override
1599                public void onChange() {
1600                    mDataAdapter.requestLoadNewPhotos();
1601                }
1602            });
1603        }
1604
1605        keepScreenOnForAWhile();
1606
1607        mPanoramaViewHelper.onResume();
1608        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1609        syncLocationManagerSetting();
1610
1611        final int previewVisibility = getPreviewVisibility();
1612        updatePreviewRendering(previewVisibility);
1613    }
1614
1615    private void fillTemporarySessions() {
1616        if (mSecureCamera) {
1617            return;
1618        }
1619        // There might be sessions still in flight (processed by our service).
1620        // Make sure they're added to the filmstrip.
1621        getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1622    }
1623
1624    @Override
1625    public void onStart() {
1626        super.onStart();
1627        mPanoramaViewHelper.onStart();
1628
1629        /*
1630         * If we're starting after launching a different Activity (lockscreen),
1631         * we need to use the last mode used in the other Activity, and
1632         * not the old one from this Activity.
1633         *
1634         * This needs to happen before CameraAppUI.resume() in order to set the
1635         * mode cover icon to the actual last mode used.
1636         *
1637         * Right now we exclude capture intents from this logic.
1638         */
1639        int modeIndex = getModeIndex();
1640        if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1641            onModeSelected(modeIndex);
1642        }
1643
1644        if (mResetToPreviewOnResume) {
1645            mCameraAppUI.resume();
1646            mResetToPreviewOnResume = false;
1647        }
1648    }
1649
1650    @Override
1651    protected void onStop() {
1652        mPanoramaViewHelper.onStop();
1653        if (mFeedbackHelper != null) {
1654            mFeedbackHelper.stopFeedback();
1655        }
1656
1657        mLocationManager.disconnect();
1658        super.onStop();
1659    }
1660
1661    @Override
1662    public void onDestroy() {
1663        if (mSecureCamera) {
1664            unregisterReceiver(mScreenOffReceiver);
1665        }
1666        mActionBar.removeOnMenuVisibilityListener(this);
1667        mSettingsManager.removeAllListeners();
1668        mCameraController.removeCallbackReceiver();
1669        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1670        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1671        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1672        mCameraAppUI.onDestroy();
1673        mModeListView.setVisibilityChangedListener(null);
1674        mCameraController = null;
1675        mSettingsManager = null;
1676        mCameraAppUI = null;
1677        mOrientationManager = null;
1678        mButtonManager = null;
1679        CameraManagerFactory.recycle();
1680        super.onDestroy();
1681    }
1682
1683    @Override
1684    public void onConfigurationChanged(Configuration config) {
1685        super.onConfigurationChanged(config);
1686        Log.v(TAG, "onConfigurationChanged");
1687        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1688            return;
1689        }
1690
1691        if (mLastLayoutOrientation != config.orientation) {
1692            mLastLayoutOrientation = config.orientation;
1693            mCurrentModule.onLayoutOrientationChanged(
1694                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1695        }
1696    }
1697
1698    @Override
1699    public boolean onKeyDown(int keyCode, KeyEvent event) {
1700        if (!mFilmstripVisible) {
1701            if (mCurrentModule.onKeyDown(keyCode, event)) {
1702                return true;
1703            }
1704            // Prevent software keyboard or voice search from showing up.
1705            if (keyCode == KeyEvent.KEYCODE_SEARCH
1706                    || keyCode == KeyEvent.KEYCODE_MENU) {
1707                if (event.isLongPress()) {
1708                    return true;
1709                }
1710            }
1711        }
1712
1713        return super.onKeyDown(keyCode, event);
1714    }
1715
1716    @Override
1717    public boolean onKeyUp(int keyCode, KeyEvent event) {
1718        if (!mFilmstripVisible) {
1719            // If a module is in the middle of capture, it should
1720            // consume the key event.
1721            if (mCurrentModule.onKeyUp(keyCode, event)) {
1722                return true;
1723            } else if (keyCode == KeyEvent.KEYCODE_MENU
1724                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1725                // Let the mode list view consume the event.
1726                mModeListView.onMenuPressed();
1727                return true;
1728            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1729                mCameraAppUI.showFilmstrip();
1730                return true;
1731            }
1732        } else {
1733            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1734                mFilmstripController.goToNextItem();
1735                return true;
1736            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1737                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1738                if (!wentToPrevious) {
1739                  // at beginning of filmstrip, hide and go back to preview
1740                  mCameraAppUI.hideFilmstrip();
1741                }
1742                return true;
1743            }
1744        }
1745        return super.onKeyUp(keyCode, event);
1746    }
1747
1748    @Override
1749    public void onBackPressed() {
1750        if (!mCameraAppUI.onBackPressed()) {
1751            if (!mCurrentModule.onBackPressed()) {
1752                super.onBackPressed();
1753            }
1754        }
1755    }
1756
1757    @Override
1758    public boolean isAutoRotateScreen() {
1759        // TODO: Move to OrientationManager.
1760        return mAutoRotateScreen;
1761    }
1762
1763    @Override
1764    public boolean onCreateOptionsMenu(Menu menu) {
1765        MenuInflater inflater = getMenuInflater();
1766        inflater.inflate(R.menu.filmstrip_menu, menu);
1767        mActionBarMenu = menu;
1768        return super.onCreateOptionsMenu(menu);
1769    }
1770
1771    protected void updateStorageSpace() {
1772        mStorageSpaceBytes = Storage.getAvailableSpace();
1773    }
1774
1775    protected long getStorageSpaceBytes() {
1776        return mStorageSpaceBytes;
1777    }
1778
1779    protected void updateStorageSpaceAndHint() {
1780        updateStorageSpace();
1781        updateStorageHint(mStorageSpaceBytes);
1782    }
1783
1784    protected void updateStorageHint(long storageSpace) {
1785        String message = null;
1786        if (storageSpace == Storage.UNAVAILABLE) {
1787            message = getString(R.string.no_storage);
1788        } else if (storageSpace == Storage.PREPARING) {
1789            message = getString(R.string.preparing_sd);
1790        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1791            message = getString(R.string.access_sd_fail);
1792        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1793            message = getString(R.string.spaceIsLow_content);
1794        }
1795
1796        if (message != null) {
1797            if (mStorageHint == null) {
1798                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1799            } else {
1800                mStorageHint.setText(message);
1801            }
1802            mStorageHint.show();
1803        } else if (mStorageHint != null) {
1804            mStorageHint.cancel();
1805            mStorageHint = null;
1806        }
1807    }
1808
1809    protected void setResultEx(int resultCode) {
1810        mResultCodeForTesting = resultCode;
1811        setResult(resultCode);
1812    }
1813
1814    protected void setResultEx(int resultCode, Intent data) {
1815        mResultCodeForTesting = resultCode;
1816        mResultDataForTesting = data;
1817        setResult(resultCode, data);
1818    }
1819
1820    public int getResultCode() {
1821        return mResultCodeForTesting;
1822    }
1823
1824    public Intent getResultData() {
1825        return mResultDataForTesting;
1826    }
1827
1828    public boolean isSecureCamera() {
1829        return mSecureCamera;
1830    }
1831
1832    @Override
1833    public boolean isPaused() {
1834        return mPaused;
1835    }
1836
1837    @Override
1838    public int getPreferredChildModeIndex(int modeIndex) {
1839        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1840            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1841            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1842                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1843            }
1844        }
1845        return modeIndex;
1846    }
1847
1848    @Override
1849    public void onModeSelected(int modeIndex) {
1850        if (mCurrentModeIndex == modeIndex) {
1851            return;
1852        }
1853
1854        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1855        // Record last used camera mode for quick switching
1856        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1857                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1858            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1859                    modeIndex);
1860        }
1861
1862        closeModule(mCurrentModule);
1863        int oldModuleIndex = mCurrentModeIndex;
1864
1865        // Select the correct module index from the mode switcher index.
1866        modeIndex = getPreferredChildModeIndex(modeIndex);
1867        setModuleFromModeIndex(modeIndex);
1868
1869        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1870        mCameraAppUI.addShutterListener(mCurrentModule);
1871        openModule(mCurrentModule);
1872        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1873        // Store the module index so we can use it the next time the Camera
1874        // starts up.
1875        mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, modeIndex);
1876    }
1877
1878    /**
1879     * Shows the settings dialog.
1880     */
1881    @Override
1882    public void onSettingsSelected() {
1883        Intent intent = new Intent(this, CameraSettingsActivity.class);
1884        startActivity(intent);
1885    }
1886
1887    /**
1888     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1889     * index an sets it as mCurrentModule.
1890     */
1891    private void setModuleFromModeIndex(int modeIndex) {
1892        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1893        if (agent == null) {
1894            return;
1895        }
1896        if (!agent.requestAppForCamera()) {
1897            mCameraController.closeCamera();
1898        }
1899        mCurrentModeIndex = agent.getModuleId();
1900        mCurrentModule = (CameraModule) agent.createModule(this);
1901    }
1902
1903    @Override
1904    public SettingsManager getSettingsManager() {
1905        return mSettingsManager;
1906    }
1907
1908    @Override
1909    public CameraServices getServices() {
1910        return (CameraServices) getApplication();
1911    }
1912
1913    public List<String> getSupportedModeNames() {
1914        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1915        List<String> supported = new ArrayList<String>();
1916
1917        for (Integer modeIndex : indices) {
1918            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1919            if (name != null && !name.equals("")) {
1920                supported.add(name);
1921            }
1922        }
1923        return supported;
1924    }
1925
1926    @Override
1927    public ButtonManager getButtonManager() {
1928        if (mButtonManager == null) {
1929            mButtonManager = new ButtonManager(this);
1930        }
1931        return mButtonManager;
1932    }
1933
1934    /**
1935     * Creates an AlertDialog appropriate for choosing whether to enable
1936     * location on the first run of the app.
1937     */
1938    public AlertDialog getFirstTimeLocationAlert() {
1939        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1940        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1941            @Override
1942            public void onCallback(Boolean locationOn) {
1943                mSettingsManager.setLocation(locationOn, mLocationManager);
1944            }
1945        });
1946        if (builder != null) {
1947            return builder.create();
1948        } else {
1949            return null;
1950        }
1951    }
1952
1953    /**
1954     * Launches an ACTION_EDIT intent for the given local data item. If
1955     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1956     * the user start either the tiny planet editor or another photo edior.
1957     *
1958     * @param data The data item to edit.
1959     */
1960    public void launchEditor(LocalData data) {
1961        Intent intent = new Intent(Intent.ACTION_EDIT)
1962                .setDataAndType(data.getUri(), data.getMimeType())
1963                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1964        try {
1965            launchActivityByIntent(intent);
1966        } catch (ActivityNotFoundException e) {
1967            launchActivityByIntent(Intent.createChooser(intent, null));
1968        }
1969    }
1970
1971    @Override
1972    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1973        super.onCreateContextMenu(menu, v, menuInfo);
1974
1975        MenuInflater inflater = getMenuInflater();
1976        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1977    }
1978
1979    @Override
1980    public boolean onContextItemSelected(MenuItem item) {
1981        switch (item.getItemId()) {
1982            case R.id.tiny_planet_editor:
1983                mMyFilmstripBottomControlListener.onTinyPlanet();
1984                return true;
1985            case R.id.photo_editor:
1986                mMyFilmstripBottomControlListener.onEdit();
1987                return true;
1988        }
1989        return false;
1990    }
1991
1992    /**
1993     * Launch the tiny planet editor.
1994     *
1995     * @param data The data must be a 360 degree stereographically mapped
1996     *            panoramic image. It will not be modified, instead a new item
1997     *            with the result will be added to the filmstrip.
1998     */
1999    public void launchTinyPlanetEditor(LocalData data) {
2000        TinyPlanetFragment fragment = new TinyPlanetFragment();
2001        Bundle bundle = new Bundle();
2002        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
2003        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
2004        fragment.setArguments(bundle);
2005        fragment.show(getFragmentManager(), "tiny_planet");
2006    }
2007
2008    private void openModule(CameraModule module) {
2009        module.init(this, isSecureCamera(), isCaptureIntent());
2010        module.hardResetSettings(mSettingsManager);
2011        module.resume();
2012        updatePreviewVisibility();
2013    }
2014
2015    private void closeModule(CameraModule module) {
2016        module.pause();
2017        mCameraAppUI.clearModuleUI();
2018    }
2019
2020    private void performDeletion() {
2021        if (!mPendingDeletion) {
2022            return;
2023        }
2024        hideUndoDeletionBar(false);
2025        mDataAdapter.executeDeletion();
2026    }
2027
2028    public void showUndoDeletionBar() {
2029        if (mPendingDeletion) {
2030            performDeletion();
2031        }
2032        Log.v(TAG, "showing undo bar");
2033        mPendingDeletion = true;
2034        if (mUndoDeletionBar == null) {
2035            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2036                    mAboveFilmstripControlLayout, true);
2037            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2038            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2039            button.setOnClickListener(new View.OnClickListener() {
2040                @Override
2041                public void onClick(View view) {
2042                    mDataAdapter.undoDataRemoval();
2043                    hideUndoDeletionBar(true);
2044                }
2045            });
2046            // Setting undo bar clickable to avoid touch events going through
2047            // the bar to the buttons (eg. edit button, etc) underneath the bar.
2048            mUndoDeletionBar.setClickable(true);
2049            // When there is user interaction going on with the undo button, we
2050            // do not want to hide the undo bar.
2051            button.setOnTouchListener(new View.OnTouchListener() {
2052                @Override
2053                public boolean onTouch(View v, MotionEvent event) {
2054                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2055                        mIsUndoingDeletion = true;
2056                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2057                        mIsUndoingDeletion = false;
2058                    }
2059                    return false;
2060                }
2061            });
2062        }
2063        mUndoDeletionBar.setAlpha(0f);
2064        mUndoDeletionBar.setVisibility(View.VISIBLE);
2065        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2066    }
2067
2068    private void hideUndoDeletionBar(boolean withAnimation) {
2069        Log.v(TAG, "Hiding undo deletion bar");
2070        mPendingDeletion = false;
2071        if (mUndoDeletionBar != null) {
2072            if (withAnimation) {
2073                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2074                        .setListener(new Animator.AnimatorListener() {
2075                            @Override
2076                            public void onAnimationStart(Animator animation) {
2077                                // Do nothing.
2078                            }
2079
2080                            @Override
2081                            public void onAnimationEnd(Animator animation) {
2082                                mUndoDeletionBar.setVisibility(View.GONE);
2083                            }
2084
2085                            @Override
2086                            public void onAnimationCancel(Animator animation) {
2087                                // Do nothing.
2088                            }
2089
2090                            @Override
2091                            public void onAnimationRepeat(Animator animation) {
2092                                // Do nothing.
2093                            }
2094                        }).start();
2095            } else {
2096                mUndoDeletionBar.setVisibility(View.GONE);
2097            }
2098        }
2099    }
2100
2101    @Override
2102    public void onOrientationChanged(int orientation) {
2103        // We keep the last known orientation. So if the user first orient
2104        // the camera then point the camera to floor or sky, we still have
2105        // the correct orientation.
2106        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2107            return;
2108        }
2109        mLastRawOrientation = orientation;
2110        if (mCurrentModule != null) {
2111            mCurrentModule.onOrientationChanged(orientation);
2112        }
2113    }
2114
2115    /**
2116     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2117     * capture intent.
2118     *
2119     * @param enable {@code true} to enable swipe.
2120     */
2121    public void setSwipingEnabled(boolean enable) {
2122        // TODO: Bring back the functionality.
2123        if (isCaptureIntent()) {
2124            // lockPreview(true);
2125        } else {
2126            // lockPreview(!enable);
2127        }
2128    }
2129
2130    // Accessor methods for getting latency times used in performance testing
2131    public long getFirstPreviewTime() {
2132        if (mCurrentModule instanceof PhotoModule) {
2133            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2134            if (coverHiddenTime != -1) {
2135                return coverHiddenTime - mOnCreateTime;
2136            }
2137        }
2138        return -1;
2139    }
2140
2141    public long getAutoFocusTime() {
2142        return (mCurrentModule instanceof PhotoModule) ?
2143                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2144    }
2145
2146    public long getShutterLag() {
2147        return (mCurrentModule instanceof PhotoModule) ?
2148                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2149    }
2150
2151    public long getShutterToPictureDisplayedTime() {
2152        return (mCurrentModule instanceof PhotoModule) ?
2153                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2154    }
2155
2156    public long getPictureDisplayedToJpegCallbackTime() {
2157        return (mCurrentModule instanceof PhotoModule) ?
2158                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2159    }
2160
2161    public long getJpegCallbackFinishTime() {
2162        return (mCurrentModule instanceof PhotoModule) ?
2163                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2164    }
2165
2166    public long getCaptureStartTime() {
2167        return (mCurrentModule instanceof PhotoModule) ?
2168                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2169    }
2170
2171    public boolean isRecording() {
2172        return (mCurrentModule instanceof VideoModule) ?
2173                ((VideoModule) mCurrentModule).isRecording() : false;
2174    }
2175
2176    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2177        return mCameraController;
2178    }
2179
2180    // For debugging purposes only.
2181    public CameraModule getCurrentModule() {
2182        return mCurrentModule;
2183    }
2184
2185    @Override
2186    public void showTutorial(AbstractTutorialOverlay tutorial) {
2187        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2188    }
2189
2190    /**
2191     * Reads the current location recording settings and passes it on to the
2192     * location manager.
2193     */
2194    public void syncLocationManagerSetting() {
2195        mSettingsManager.syncLocationManager(mLocationManager);
2196    }
2197
2198    private void keepScreenOnForAWhile() {
2199        if (mKeepScreenOn) {
2200            return;
2201        }
2202        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2203        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2204        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2205    }
2206
2207    private void resetScreenOn() {
2208        mKeepScreenOn = false;
2209        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2210        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2211    }
2212
2213    /**
2214     * @return {@code true} if the Gallery is launched successfully.
2215     */
2216    private boolean startGallery() {
2217        if (mGalleryIntent == null) {
2218            return false;
2219        }
2220        try {
2221            UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2222                    InteractionCause.BUTTON);
2223            Intent startGalleryIntent = new Intent(mGalleryIntent);
2224            int currentDataId = mFilmstripController.getCurrentId();
2225            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2226            if (currentLocalData != null) {
2227                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2228            }
2229            launchActivityByIntent(startGalleryIntent);
2230        } catch (ActivityNotFoundException e) {
2231            Log.w(TAG, "Failed to launch gallery activity, closing");
2232        }
2233        return false;
2234    }
2235
2236    private void setNfcBeamPushUriFromData(LocalData data) {
2237        final Uri uri = data.getUri();
2238        if (uri != Uri.EMPTY) {
2239            mNfcPushUris[0] = uri;
2240        } else {
2241            mNfcPushUris[0] = null;
2242        }
2243    }
2244
2245    /**
2246     * Updates the visibility of the filmstrip bottom controls and action bar.
2247     */
2248    private void updateUiByData(final int dataId) {
2249        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2250        if (currentData == null) {
2251            Log.w(TAG, "Current data ID not found.");
2252            hideSessionProgress();
2253            return;
2254        }
2255        updateActionBarMenu(currentData);
2256
2257        /* Bottom controls. */
2258        updateBottomControlsByData(currentData);
2259
2260        if (isSecureCamera()) {
2261            // We cannot show buttons in secure camera since go to other
2262            // activities might create a security hole.
2263            mCameraAppUI.getFilmstripBottomControls().hideControls();
2264            return;
2265        }
2266
2267
2268        setNfcBeamPushUriFromData(currentData);
2269
2270        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2271            mDataAdapter.updateMetadata(dataId);
2272        }
2273    }
2274
2275    /**
2276     * Updates the bottom controls based on the data.
2277     */
2278    private void updateBottomControlsByData(final LocalData currentData) {
2279
2280        final CameraAppUI.BottomPanel filmstripBottomPanel =
2281                mCameraAppUI.getFilmstripBottomControls();
2282        filmstripBottomPanel.showControls();
2283        filmstripBottomPanel.setEditButtonVisibility(
2284                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2285        filmstripBottomPanel.setShareButtonVisibility(
2286                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2287        filmstripBottomPanel.setDeleteButtonVisibility(
2288                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2289
2290        /* Progress bar */
2291
2292        Uri contentUri = currentData.getUri();
2293        CaptureSessionManager sessionManager = getServices()
2294                .getCaptureSessionManager();
2295
2296        if (sessionManager.hasErrorMessage(contentUri)) {
2297            showProcessError(sessionManager.getErrorMesage(contentUri));
2298        } else {
2299            filmstripBottomPanel.hideProgressError();
2300            CaptureSession session = sessionManager.getSession(contentUri);
2301
2302            if (session != null) {
2303                int sessionProgress = session.getProgress();
2304
2305                if (sessionProgress < 0) {
2306                    hideSessionProgress();
2307                } else {
2308                    CharSequence progressMessage = session.getProgressMessage();
2309                    showSessionProgress(progressMessage);
2310                    updateSessionProgress(sessionProgress);
2311                }
2312            } else {
2313                hideSessionProgress();
2314            }
2315        }
2316
2317        /* View button */
2318
2319        // We need to add this to a separate DB.
2320        final int viewButtonVisibility;
2321        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2322            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2323        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2324            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2325        } else {
2326            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2327        }
2328
2329        filmstripBottomPanel.setTinyPlanetEnabled(
2330                PanoramaMetadataLoader.isPanorama360(currentData));
2331        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2332    }
2333
2334    private class PeekAnimationHandler extends Handler {
2335        private class DataAndCallback {
2336            LocalData mData;
2337            com.android.camera.util.Callback<Bitmap> mCallback;
2338
2339            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2340                    callback) {
2341                mData = data;
2342                mCallback = callback;
2343            }
2344        }
2345
2346        public PeekAnimationHandler(Looper looper) {
2347            super(looper);
2348        }
2349
2350        /**
2351         * Starts the animation decoding job and posts a {@code Runnable} back
2352         * when when the decoding is done.
2353         *
2354         * @param data The data item to decode the thumbnail for.
2355         * @param callback {@link com.android.camera.util.Callback} after the
2356         *                 decoding is done.
2357         */
2358        public void startDecodingJob(final LocalData data,
2359                final com.android.camera.util.Callback<Bitmap> callback) {
2360            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2361                    new DataAndCallback(data, callback)).sendToTarget();
2362        }
2363
2364        @Override
2365        public void handleMessage(Message msg) {
2366            final LocalData data = ((DataAndCallback) msg.obj).mData;
2367            final com.android.camera.util.Callback<Bitmap> callback =
2368                    ((DataAndCallback) msg.obj).mCallback;
2369            if (data == null || callback == null) {
2370                return;
2371            }
2372
2373            final Bitmap bitmap;
2374            switch (data.getLocalDataType()) {
2375                case LocalData.LOCAL_IN_PROGRESS_DATA:
2376                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2377                    if (jpegData != null) {
2378                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2379                    } else {
2380                        bitmap = null;
2381                    }
2382                    break;
2383
2384                case LocalData.LOCAL_IMAGE:
2385                    FileInputStream stream;
2386                    try {
2387                        stream = new FileInputStream(data.getPath());
2388                    } catch (FileNotFoundException e) {
2389                        Log.e(TAG, "File not found:" + data.getPath());
2390                        return;
2391                    }
2392                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2393                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2394                            mAboveFilmstripControlLayout.getMeasuredHeight());
2395                    if (data.getRotation() % 180 != 0) {
2396                        int dummy = dim.x;
2397                        dim.x = dim.y;
2398                        dim.y = dummy;
2399                    }
2400                    bitmap = LocalDataUtil
2401                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2402                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2403                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2404                    break;
2405
2406                case LocalData.LOCAL_VIDEO:
2407                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2408                    break;
2409
2410                default:
2411                    bitmap = null;
2412                    break;
2413            }
2414
2415            if (bitmap == null) {
2416                return;
2417            }
2418
2419            mMainHandler.post(new Runnable() {
2420                @Override
2421                public void run() {
2422                    callback.onCallback(bitmap);
2423                }
2424            });
2425        }
2426    }
2427
2428    private void showDetailsDialog(int dataId) {
2429        final LocalData data = mDataAdapter.getLocalData(dataId);
2430        if (data == null) {
2431            return;
2432        }
2433        MediaDetails details = data.getMediaDetails(getAndroidContext());
2434        if (details == null) {
2435            return;
2436        }
2437        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2438        detailDialog.show();
2439
2440        UsageStatistics.instance().photoInteraction(
2441                UsageStatistics.hashFileName(fileNameFromDataID(dataId)),
2442                eventprotos.CameraEvent.InteractionType.DETAILS,
2443                InteractionCause.BUTTON);
2444    }
2445
2446    /**
2447     * Show or hide action bar items depending on current data type.
2448     */
2449    private void updateActionBarMenu(LocalData data) {
2450        if (mActionBarMenu == null) {
2451            return;
2452        }
2453
2454        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2455        if (detailsMenuItem == null) {
2456            return;
2457        }
2458
2459        int type = data.getLocalDataType();
2460        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2461        detailsMenuItem.setVisible(showDetails);
2462    }
2463}
2464