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