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