CameraActivity.java revision e5a7c3cbb3aa2f0d28dba21579b530bdfe1331fe
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, null);
486        CameraUtil.showErrorAndFinish(this,
487                R.string.camera_disabled);
488    }
489
490    @Override
491    public void onDeviceOpenFailure(int cameraId, String info) {
492        UsageStatistics.instance().cameraFailure(
493                eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info);
494        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
495    }
496
497    @Override
498    public void onDeviceOpenedAlready(int cameraId) {
499        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
500    }
501
502    @Override
503    public void onReconnectionFailure(CameraManager mgr) {
504        UsageStatistics.instance().cameraFailure(
505                eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null);
506        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
507    }
508
509    private static class MainHandler extends Handler {
510        final WeakReference<CameraActivity> mActivity;
511
512        public MainHandler(CameraActivity activity, Looper looper) {
513            super(looper);
514            mActivity = new WeakReference<CameraActivity>(activity);
515        }
516
517        @Override
518        public void handleMessage(Message msg) {
519            CameraActivity activity = mActivity.get();
520            if (activity == null) {
521                return;
522            }
523            switch (msg.what) {
524
525                case MSG_CLEAR_SCREEN_ON_FLAG: {
526                    if (!activity.mPaused) {
527                        activity.getWindow().clearFlags(
528                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
529                    }
530                    break;
531                }
532            }
533        }
534    }
535
536    private String fileNameFromDataID(int dataID) {
537        final LocalData localData = mDataAdapter.getLocalData(dataID);
538
539        File localFile = new File(localData.getPath());
540        return localFile.getName();
541    }
542
543    private final FilmstripContentPanel.Listener mFilmstripListener =
544            new FilmstripContentPanel.Listener() {
545
546                @Override
547                public void onSwipeOut() {
548                }
549
550                @Override
551                public void onSwipeOutBegin() {
552                    mActionBar.hide();
553                    mFilmstripCoversPreview = false;
554                    updatePreviewVisibility();
555                }
556
557                @Override
558                public void onFilmstripHidden() {
559                    mFilmstripVisible = false;
560                    UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
561                            NavigationChange.InteractionCause.SWIPE_RIGHT);
562                    CameraActivity.this.setFilmstripUiVisibility(false);
563                    // When the user hide the filmstrip (either swipe out or
564                    // tap on back key) we move to the first item so next time
565                    // when the user swipe in the filmstrip, the most recent
566                    // one is shown.
567                    mFilmstripController.goToFirstItem();
568                }
569
570                @Override
571                public void onFilmstripShown() {
572                    mFilmstripVisible = true;
573                    UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
574                            NavigationChange.InteractionCause.SWIPE_LEFT);
575                    updateUiByData(mFilmstripController.getCurrentId());
576                }
577
578                @Override
579                public void onFocusedDataLongPressed(int dataId) {
580                    // Do nothing.
581                }
582
583                @Override
584                public void onFocusedDataPromoted(int dataID) {
585                    UsageStatistics.instance().mediaInteraction(fileNameFromDataID(dataID),
586                            MediaInteraction.InteractionType.DELETE,
587                            NavigationChange.InteractionCause.SWIPE_UP);
588                    removeData(dataID);
589                }
590
591                @Override
592                public void onFocusedDataDemoted(int dataID) {
593                    UsageStatistics.instance().mediaInteraction(fileNameFromDataID(dataID),
594                            MediaInteraction.InteractionType.DELETE,
595                            NavigationChange.InteractionCause.SWIPE_DOWN);
596                    removeData(dataID);
597                }
598
599                @Override
600                public void onEnterFullScreenUiShown(int dataId) {
601                    if (mFilmstripVisible) {
602                        CameraActivity.this.setFilmstripUiVisibility(true);
603                    }
604                }
605
606                @Override
607                public void onLeaveFullScreenUiShown(int dataId) {
608                    // Do nothing.
609                }
610
611                @Override
612                public void onEnterFullScreenUiHidden(int dataId) {
613                    if (mFilmstripVisible) {
614                        CameraActivity.this.setFilmstripUiVisibility(false);
615                    }
616                }
617
618                @Override
619                public void onLeaveFullScreenUiHidden(int dataId) {
620                    // Do nothing.
621                }
622
623                @Override
624                public void onEnterFilmstrip(int dataId) {
625                    if (mFilmstripVisible) {
626                        CameraActivity.this.setFilmstripUiVisibility(true);
627                    }
628                }
629
630                @Override
631                public void onLeaveFilmstrip(int dataId) {
632                    // Do nothing.
633                }
634
635                @Override
636                public void onDataReloaded() {
637                    if (!mFilmstripVisible) {
638                        return;
639                    }
640                    updateUiByData(mFilmstripController.getCurrentId());
641                }
642
643                @Override
644                public void onDataUpdated(int dataId) {
645                    if (!mFilmstripVisible) {
646                        return;
647                    }
648                    updateUiByData(mFilmstripController.getCurrentId());
649                }
650
651                @Override
652                public void onEnterZoomView(int dataID) {
653                    if (mFilmstripVisible) {
654                        CameraActivity.this.setFilmstripUiVisibility(false);
655                    }
656                }
657
658                @Override
659                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
660                    if (!mFilmstripVisible) {
661                        return;
662                    }
663                    // TODO: This callback is UI event callback, should always
664                    // happen on UI thread. Find the reason for this
665                    // runOnUiThread() and fix it.
666                    runOnUiThread(new Runnable() {
667                        @Override
668                        public void run() {
669                            updateUiByData(newDataId);
670                        }
671                    });
672                }
673
674                @Override
675                public void onScroll(int firstVisiblePosition, int visibleItemCount, int totalItemCount) {
676                    mPreloader.onScroll(null /*absListView*/, firstVisiblePosition, visibleItemCount, totalItemCount);
677                }
678            };
679
680    private final LocalDataAdapter.LocalDataListener mLocalDataListener =
681            new LocalDataAdapter.LocalDataListener() {
682                @Override
683                public void onMetadataUpdated(List<Integer> updatedData) {
684                    int currentDataId = mFilmstripController.getCurrentId();
685                    for (Integer dataId : updatedData) {
686                        if (dataId == currentDataId) {
687                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
688                        }
689                    }
690                }
691            };
692
693    public void gotoGallery() {
694        UsageStatistics.instance().changeScreen(NavigationChange.Mode.FILMSTRIP,
695                NavigationChange.InteractionCause.BUTTON);
696
697        mFilmstripController.goToNextItem();
698    }
699
700    /**
701     * If 'visible' is false, this hides the action bar. Also maintains
702     * lights-out at all times.
703     *
704     * @param visible is false, this hides the action bar and filmstrip bottom
705     *            controls.
706     */
707    private void setFilmstripUiVisibility(boolean visible) {
708        mLightsOutRunnable.run();
709        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
710        if (visible != mActionBar.isShowing()) {
711            if (visible) {
712                mActionBar.show();
713            } else {
714                mActionBar.hide();
715            }
716        }
717        mFilmstripCoversPreview = visible;
718        updatePreviewVisibility();
719    }
720
721    private void hideSessionProgress() {
722        mCameraAppUI.getFilmstripBottomControls().hideProgress();
723    }
724
725    private void showSessionProgress(CharSequence message) {
726        CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
727        controls.setProgressText(message);
728        controls.hideControls();
729        controls.hideProgressError();
730        controls.showProgress();
731    }
732
733    private void showProcessError(CharSequence message) {
734        mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
735    }
736
737    private void updateSessionProgress(int progress) {
738        mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
739    }
740
741    private void updateSessionProgressText(CharSequence message) {
742        mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
743    }
744
745    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
746    private void setupNfcBeamPush() {
747        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
748        if (adapter == null) {
749            return;
750        }
751
752        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
753            // Disable beaming
754            adapter.setNdefPushMessage(null, CameraActivity.this);
755            return;
756        }
757
758        adapter.setBeamPushUris(null, CameraActivity.this);
759        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
760            @Override
761            public Uri[] createBeamUris(NfcEvent event) {
762                return mNfcPushUris;
763            }
764        }, CameraActivity.this);
765    }
766
767    @Override
768    public void onMenuVisibilityChanged(boolean isVisible) {
769        // TODO: Remove this or bring back the original implementation: cancel
770        // auto-hide actionbar.
771    }
772
773    @Override
774    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
775        int currentDataId = mFilmstripController.getCurrentId();
776        if (currentDataId < 0) {
777            return false;
778        }
779        UsageStatistics.instance().mediaInteraction(fileNameFromDataID(currentDataId),
780                MediaInteraction.InteractionType.SHARE,
781                NavigationChange.InteractionCause.BUTTON);
782        // TODO add intent.getComponent().getPackageName()
783        return true;
784    }
785
786    // Note: All callbacks come back on the main thread.
787    private final SessionListener mSessionListener =
788            new SessionListener() {
789                @Override
790                public void onSessionQueued(final Uri uri) {
791                    if (!Storage.isSessionUri(uri)) {
792                        return;
793                    }
794                    LocalSessionData newData = new LocalSessionData(uri);
795                    mDataAdapter.addData(newData);
796                }
797
798                @Override
799                public void onSessionDone(final Uri sessionUri) {
800                    Log.v(TAG, "onSessionDone:" + sessionUri);
801                    Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
802                    if (contentUri == null) {
803                        mDataAdapter.refresh(sessionUri);
804                        return;
805                    }
806                    LocalData newData = LocalMediaData.PhotoData.fromContentUri(
807                            getContentResolver(), contentUri);
808
809                    final int pos = mDataAdapter.findDataByContentUri(sessionUri);
810                    if (pos == -1) {
811                        // We do not have a placeholder for this image, perhaps
812                        // due to the activity crashing or being killed.
813                        mDataAdapter.addData(newData);
814                    } else {
815                        mDataAdapter.updateData(pos, newData);
816                    }
817                }
818
819                @Override
820                public void onSessionProgress(final Uri uri, final int progress) {
821                    if (progress < 0) {
822                        // Do nothing, there is no task for this URI.
823                        return;
824                    }
825                    int currentDataId = mFilmstripController.getCurrentId();
826                    if (currentDataId == -1) {
827                        return;
828                    }
829                    if (uri.equals(
830                            mDataAdapter.getLocalData(currentDataId).getUri())) {
831                        updateSessionProgress(progress);
832                    }
833                }
834
835                @Override
836                public void onSessionProgressText(final Uri uri, final CharSequence message) {
837                    int currentDataId = mFilmstripController.getCurrentId();
838                    if (currentDataId == -1) {
839                        return;
840                    }
841                    if (uri.equals(
842                            mDataAdapter.getLocalData(currentDataId).getUri())) {
843                        updateSessionProgressText(message);
844                    }
845                }
846
847                @Override
848                public void onSessionUpdated(Uri uri) {
849                    mDataAdapter.refresh(uri);
850                }
851
852                @Override
853                public void onSessionPreviewAvailable(Uri uri) {
854                    mDataAdapter.refresh(uri);
855                    int dataId = mDataAdapter.findDataByContentUri(uri);
856                    if (dataId != -1) {
857                        startPeekAnimation(mDataAdapter.getLocalData(dataId));
858                    }
859                }
860
861                @Override
862                public void onSessionFailed(Uri uri, CharSequence reason) {
863                    Log.v(TAG, "onSessionFailed:" + uri);
864
865                    int failedDataId = mDataAdapter.findDataByContentUri(uri);
866                    int currentDataId = mFilmstripController.getCurrentId();
867
868                    if (currentDataId == failedDataId) {
869                        updateSessionProgress(0);
870                        showProcessError(reason);
871                    }
872                    // HERE
873                    mDataAdapter.refresh(uri);
874                }
875            };
876
877    @Override
878    public Context getAndroidContext() {
879        return mAppContext;
880    }
881
882    @Override
883    public void launchActivityByIntent(Intent intent) {
884        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
885    }
886
887    @Override
888    public int getCurrentModuleIndex() {
889        return mCurrentModeIndex;
890    }
891
892    @Override
893    public ModuleController getCurrentModuleController() {
894        return mCurrentModule;
895    }
896
897    @Override
898    public int getQuickSwitchToModuleId(int currentModuleIndex) {
899        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
900                mAppContext);
901    }
902
903    @Override
904    public SurfaceTexture getPreviewBuffer() {
905        // TODO: implement this
906        return null;
907    }
908
909    @Override
910    public void onPreviewReadyToStart() {
911        mCameraAppUI.onPreviewReadyToStart();
912    }
913
914    @Override
915    public void onPreviewStarted() {
916        mCameraAppUI.onPreviewStarted();
917    }
918
919    @Override
920    public void addPreviewAreaSizeChangedListener(
921            PreviewStatusListener.PreviewAreaChangedListener listener) {
922        mCameraAppUI.addPreviewAreaChangedListener(listener);
923    }
924
925    @Override
926    public void removePreviewAreaSizeChangedListener(
927            PreviewStatusListener.PreviewAreaChangedListener listener) {
928        mCameraAppUI.removePreviewAreaChangedListener(listener);
929    }
930
931    @Override
932    public void setupOneShotPreviewListener() {
933        mCameraController.setOneShotPreviewCallback(mMainHandler,
934                new CameraManager.CameraPreviewDataCallback() {
935                    @Override
936                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
937                        mCurrentModule.onPreviewInitialDataReceived();
938                        mCameraAppUI.onNewPreviewFrame();
939                    }
940                }
941        );
942    }
943
944    @Override
945    public void updatePreviewAspectRatio(float aspectRatio) {
946        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
947    }
948
949    @Override
950    public void updatePreviewTransform(Matrix matrix) {
951        mCameraAppUI.updatePreviewTransform(matrix);
952    }
953
954    @Override
955    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
956        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
957    }
958
959    @Override
960    public FrameLayout getModuleLayoutRoot() {
961        return mCameraAppUI.getModuleRootView();
962    }
963
964    @Override
965    public void setShutterEventsListener(ShutterEventsListener listener) {
966        // TODO: implement this
967    }
968
969    @Override
970    public void setShutterEnabled(boolean enabled) {
971        mCameraAppUI.setShutterButtonEnabled(enabled);
972    }
973
974    @Override
975    public boolean isShutterEnabled() {
976        return mCameraAppUI.isShutterButtonEnabled();
977    }
978
979    @Override
980    public void startPreCaptureAnimation() {
981        mCameraAppUI.startPreCaptureAnimation();
982    }
983
984    @Override
985    public void cancelPreCaptureAnimation() {
986        // TODO: implement this
987    }
988
989    @Override
990    public void startPostCaptureAnimation() {
991        // TODO: implement this
992    }
993
994    @Override
995    public void startPostCaptureAnimation(Bitmap thumbnail) {
996        // TODO: implement this
997    }
998
999    @Override
1000    public void cancelPostCaptureAnimation() {
1001        // TODO: implement this
1002    }
1003
1004    @Override
1005    public OrientationManager getOrientationManager() {
1006        return mOrientationManager;
1007    }
1008
1009    @Override
1010    public LocationManager getLocationManager() {
1011        return mLocationManager;
1012    }
1013
1014    @Override
1015    public void lockOrientation() {
1016        if (mOrientationManager != null) {
1017            mOrientationManager.lockOrientation();
1018        }
1019    }
1020
1021    @Override
1022    public void unlockOrientation() {
1023        if (mOrientationManager != null) {
1024            mOrientationManager.unlockOrientation();
1025        }
1026    }
1027
1028    /**
1029     * Starts the filmstrip peek animation if the filmstrip is not visible.
1030     * Only {@link LocalData#LOCAL_IMAGE}, {@link
1031     * LocalData#LOCAL_IN_PROGRESS_DATA} and {@link
1032     * LocalData#LOCAL_VIDEO} are supported.
1033     *
1034     * @param data The data to peek.
1035     */
1036    private void startPeekAnimation(final LocalData data) {
1037        if (mFilmstripVisible || mPeekAnimationHandler == null) {
1038            return;
1039        }
1040
1041        int dataType = data.getLocalDataType();
1042        if (dataType != LocalData.LOCAL_IMAGE && dataType != LocalData.LOCAL_IN_PROGRESS_DATA &&
1043                dataType != LocalData.LOCAL_VIDEO) {
1044            return;
1045        }
1046
1047        mPeekAnimationHandler.startDecodingJob(data, new Callback<Bitmap>() {
1048            @Override
1049            public void onCallback(Bitmap result) {
1050                mCameraAppUI.startPeekAnimation(result, true);
1051            }
1052        });
1053    }
1054
1055    @Override
1056    public void notifyNewMedia(Uri uri) {
1057        updateStorageSpaceAndHint(null);
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        mCameraAppUI.onPreviewVisiblityChanged(visibility);
1380        updatePreviewRendering(visibility);
1381        mCurrentModule.onPreviewVisibilityChanged(visibility);
1382    }
1383
1384    private void updatePreviewRendering(int visibility) {
1385        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1386            mCameraAppUI.pausePreviewRendering();
1387        } else {
1388            mCameraAppUI.resumePreviewRendering();
1389        }
1390    }
1391
1392    private int getPreviewVisibility() {
1393        if (mFilmstripCoversPreview) {
1394            return ModuleController.VISIBILITY_HIDDEN;
1395        } else if (mModeListVisible){
1396            return ModuleController.VISIBILITY_COVERED;
1397        } else {
1398            return ModuleController.VISIBILITY_VISIBLE;
1399        }
1400    }
1401
1402    private void setRotationAnimation() {
1403        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1404        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1405        Window win = getWindow();
1406        WindowManager.LayoutParams winParams = win.getAttributes();
1407        winParams.rotationAnimation = rotationAnimation;
1408        win.setAttributes(winParams);
1409    }
1410
1411    @Override
1412    public void onUserInteraction() {
1413        super.onUserInteraction();
1414        if (!isFinishing()) {
1415            keepScreenOnForAWhile();
1416        }
1417    }
1418
1419    @Override
1420    public boolean dispatchTouchEvent(MotionEvent ev) {
1421        boolean result = super.dispatchTouchEvent(ev);
1422        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1423            // Real deletion is postponed until the next user interaction after
1424            // the gesture that triggers deletion. Until real deletion is
1425            // performed, users can click the undo button to bring back the
1426            // image that they chose to delete.
1427            if (mPendingDeletion && !mIsUndoingDeletion) {
1428                performDeletion();
1429            }
1430        }
1431        return result;
1432    }
1433
1434    @Override
1435    public void onPause() {
1436        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1437
1438        /*
1439         * Save the last module index after all secure camera and icon launches,
1440         * not just on mode switches.
1441         *
1442         * Right now we exclude capture intents from this logic, because we also
1443         * ignore the cross-Activity recovery logic in onStart for capture intents.
1444         */
1445        if (!isCaptureIntent()) {
1446            mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX,
1447                mCurrentModeIndex);
1448        }
1449
1450        mPaused = true;
1451        mPeekAnimationHandler = null;
1452        mPeekAnimationThread.quitSafely();
1453        mPeekAnimationThread = null;
1454
1455        // Delete photos that are pending deletion
1456        performDeletion();
1457        mCurrentModule.pause();
1458        mOrientationManager.pause();
1459        // Close the camera and wait for the operation done.
1460        mCameraController.closeCamera();
1461        mPanoramaViewHelper.onPause();
1462
1463        mLocalImagesObserver.setForegroundChangeListener(null);
1464        mLocalImagesObserver.setActivityPaused(true);
1465        mLocalVideosObserver.setActivityPaused(true);
1466        mPreloader.cancelAllLoads();
1467        resetScreenOn();
1468        super.onPause();
1469    }
1470
1471    @Override
1472    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1473        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1474            mResetToPreviewOnResume = false;
1475        } else {
1476            super.onActivityResult(requestCode, resultCode, data);
1477        }
1478    }
1479
1480    @Override
1481    public void onResume() {
1482        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1483        Log.v(TAG, "Build info: " + Build.DISPLAY);
1484
1485        mPaused = false;
1486        updateStorageSpaceAndHint(null);
1487
1488        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1489
1490        // TODO: Handle this in OrientationManager.
1491        // Auto-rotate off
1492        if (Settings.System.getInt(getContentResolver(),
1493                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1494            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1495            mAutoRotateScreen = false;
1496        } else {
1497            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1498            mAutoRotateScreen = true;
1499        }
1500
1501        String action = getIntent().getAction();
1502        // Foreground event logging
1503        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)) {
1504            UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_VIDEO_CAPTURE,
1505                    currentUserInterfaceMode());
1506        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)) {
1507            UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_IMAGE_CAPTURE,
1508                    currentUserInterfaceMode());
1509        } else if (MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(action) ||
1510                INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
1511            // logged in onCreate()
1512        } else if (Intent.ACTION_MAIN.equals(action)) {
1513            UsageStatistics.instance().foregrounded(ForegroundSource.ACTION_MAIN,
1514                    currentUserInterfaceMode());
1515        } else {
1516            UsageStatistics.instance().foregrounded(ForegroundSource.UNKNOWN_SOURCE,
1517                    currentUserInterfaceMode());
1518        }
1519
1520        Drawable galleryLogo;
1521        if (mSecureCamera) {
1522            mGalleryIntent = null;
1523            galleryLogo = null;
1524        } else {
1525            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1526            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1527        }
1528        if (galleryLogo == null) {
1529            try {
1530                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1531            } catch (PackageManager.NameNotFoundException e) {
1532                Log.e(TAG, "Can't get the activity logo");
1533            }
1534        }
1535        if (mGalleryIntent != null) {
1536            mActionBar.setDisplayUseLogoEnabled(true);
1537        }
1538        mActionBar.setLogo(galleryLogo);
1539        mOrientationManager.resume();
1540        super.onResume();
1541        mPeekAnimationThread = new HandlerThread("Peek animation");
1542        mPeekAnimationThread.start();
1543        mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
1544
1545        mCurrentModule.hardResetSettings(mSettingsManager);
1546        mCurrentModule.resume();
1547        UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
1548                NavigationChange.InteractionCause.BUTTON);
1549        setSwipingEnabled(true);
1550
1551        if (!mResetToPreviewOnResume) {
1552            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1553            if (data != null) {
1554                mDataAdapter.refresh(data.getUri());
1555            }
1556        }
1557        // The share button might be disabled to avoid double tapping.
1558        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1559        // Default is showing the preview, unless disabled by explicitly
1560        // starting an activity we want to return from to the filmstrip rather
1561        // than the preview.
1562        mResetToPreviewOnResume = true;
1563
1564        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1565                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1566            if (!mSecureCamera) {
1567                // If it's secure camera, requestLoad() should not be called
1568                // as it will load all the data.
1569                if (!mFilmstripVisible) {
1570                    mDataAdapter.requestLoad(new Callback<Void>() {
1571                        @Override
1572                        public void onCallback(Void result) {
1573                            fillTemporarySessions();
1574                        }
1575                    });
1576                } else {
1577                    mDataAdapter.requestLoadNewPhotos();
1578                }
1579            }
1580        }
1581        mLocalImagesObserver.setActivityPaused(false);
1582        mLocalVideosObserver.setActivityPaused(false);
1583        if (!mSecureCamera) {
1584            mLocalImagesObserver.setForegroundChangeListener(
1585                    new LocalMediaObserver.ChangeListener() {
1586                @Override
1587                public void onChange() {
1588                    mDataAdapter.requestLoadNewPhotos();
1589                }
1590            });
1591        }
1592
1593        keepScreenOnForAWhile();
1594
1595        // Lights-out mode at all times.
1596        final View rootView = findViewById(R.id.activity_root_view);
1597        mLightsOutRunnable.run();
1598        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
1599                new OnSystemUiVisibilityChangeListener() {
1600                    @Override
1601                    public void onSystemUiVisibilityChange(int visibility) {
1602                        mMainHandler.removeCallbacks(mLightsOutRunnable);
1603                        mMainHandler.postDelayed(mLightsOutRunnable, LIGHTS_OUT_DELAY_MS);
1604                    }
1605                });
1606
1607        mPanoramaViewHelper.onResume();
1608        ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
1609        syncLocationManagerSetting();
1610
1611        final int previewVisibility = getPreviewVisibility();
1612        updatePreviewRendering(previewVisibility);
1613    }
1614
1615    private void fillTemporarySessions() {
1616        if (mSecureCamera) {
1617            return;
1618        }
1619        // There might be sessions still in flight (processed by our service).
1620        // Make sure they're added to the filmstrip.
1621        getServices().getCaptureSessionManager().fillTemporarySession(mSessionListener);
1622    }
1623
1624    @Override
1625    public void onStart() {
1626        super.onStart();
1627        mPanoramaViewHelper.onStart();
1628
1629        /*
1630         * If we're starting after launching a different Activity (lockscreen),
1631         * we need to use the last mode used in the other Activity, and
1632         * not the old one from this Activity.
1633         *
1634         * This needs to happen before CameraAppUI.resume() in order to set the
1635         * mode cover icon to the actual last mode used.
1636         *
1637         * Right now we exclude capture intents from this logic.
1638         */
1639        int modeIndex = getModeIndex();
1640        if (!isCaptureIntent() && mCurrentModeIndex != modeIndex) {
1641            onModeSelected(modeIndex);
1642        }
1643
1644        if (mResetToPreviewOnResume) {
1645            mCameraAppUI.resume();
1646            mResetToPreviewOnResume = false;
1647        }
1648    }
1649
1650    @Override
1651    protected void onStop() {
1652        mPanoramaViewHelper.onStop();
1653        if (mFeedbackHelper != null) {
1654            mFeedbackHelper.stopFeedback();
1655        }
1656
1657        mLocationManager.disconnect();
1658        super.onStop();
1659    }
1660
1661    @Override
1662    public void onDestroy() {
1663        if (mSecureCamera) {
1664            unregisterReceiver(mScreenOffReceiver);
1665        }
1666        mActionBar.removeOnMenuVisibilityListener(this);
1667        mSettingsManager.removeAllListeners();
1668        mCameraController.removeCallbackReceiver();
1669        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1670        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1671        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1672        mCameraAppUI.onDestroy();
1673        mModeListView.setVisibilityChangedListener(null);
1674        mCameraController = null;
1675        mSettingsManager = null;
1676        mCameraAppUI = null;
1677        mOrientationManager = null;
1678        mButtonManager = null;
1679        CameraManagerFactory.recycle();
1680        super.onDestroy();
1681    }
1682
1683    @Override
1684    public void onConfigurationChanged(Configuration config) {
1685        super.onConfigurationChanged(config);
1686        Log.v(TAG, "onConfigurationChanged");
1687        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1688            return;
1689        }
1690
1691        if (mLastLayoutOrientation != config.orientation) {
1692            mLastLayoutOrientation = config.orientation;
1693            mCurrentModule.onLayoutOrientationChanged(
1694                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1695        }
1696    }
1697
1698    @Override
1699    public boolean onKeyDown(int keyCode, KeyEvent event) {
1700        if (!mFilmstripVisible) {
1701            if (mCurrentModule.onKeyDown(keyCode, event)) {
1702                return true;
1703            }
1704            // Prevent software keyboard or voice search from showing up.
1705            if (keyCode == KeyEvent.KEYCODE_SEARCH
1706                    || keyCode == KeyEvent.KEYCODE_MENU) {
1707                if (event.isLongPress()) {
1708                    return true;
1709                }
1710            }
1711        }
1712
1713        return super.onKeyDown(keyCode, event);
1714    }
1715
1716    @Override
1717    public boolean onKeyUp(int keyCode, KeyEvent event) {
1718        if (!mFilmstripVisible) {
1719            // If a module is in the middle of capture, it should
1720            // consume the key event.
1721            if (mCurrentModule.onKeyUp(keyCode, event)) {
1722                return true;
1723            } else if (keyCode == KeyEvent.KEYCODE_MENU
1724                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1725                // Let the mode list view consume the event.
1726                mModeListView.onMenuPressed();
1727                return true;
1728            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1729                mCameraAppUI.showFilmstrip();
1730                return true;
1731            }
1732        } else {
1733            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1734                mFilmstripController.goToNextItem();
1735                return true;
1736            } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
1737                boolean wentToPrevious = mFilmstripController.goToPreviousItem();
1738                if (!wentToPrevious) {
1739                  // at beginning of filmstrip, hide and go back to preview
1740                  mCameraAppUI.hideFilmstrip();
1741                }
1742                return true;
1743            }
1744        }
1745        return super.onKeyUp(keyCode, event);
1746    }
1747
1748    @Override
1749    public void onBackPressed() {
1750        if (!mCameraAppUI.onBackPressed()) {
1751            if (!mCurrentModule.onBackPressed()) {
1752                super.onBackPressed();
1753            }
1754        }
1755    }
1756
1757    @Override
1758    public boolean isAutoRotateScreen() {
1759        // TODO: Move to OrientationManager.
1760        return mAutoRotateScreen;
1761    }
1762
1763    @Override
1764    public boolean onCreateOptionsMenu(Menu menu) {
1765        MenuInflater inflater = getMenuInflater();
1766        inflater.inflate(R.menu.filmstrip_menu, menu);
1767        mActionBarMenu = menu;
1768        return super.onCreateOptionsMenu(menu);
1769    }
1770
1771    protected long getStorageSpaceBytes() {
1772        synchronized (mStorageSpaceLock) {
1773            return mStorageSpaceBytes;
1774        }
1775    }
1776
1777    protected interface OnStorageUpdateDoneListener {
1778        public void onStorageUpdateDone(long bytes);
1779    }
1780
1781    protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
1782        /*
1783         * We execute disk operations on a background thread in order to
1784         * free up the UI thread.  Synchronizing on the lock below ensures
1785         * that when getStorageSpaceBytes is called, the main thread waits
1786         * until this method has completed.
1787         *
1788         * However, .execute() does not ensure this execution block will be
1789         * run right away (.execute() schedules this AsyncTask for sometime
1790         * in the future. executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
1791         * tries to execute the task in parellel with other AsyncTasks, but
1792         * there's still no guarantee).
1793         * e.g. don't call this then immediately call getStorageSpaceBytes().
1794         * Instead, pass in an OnStorageUpdateDoneListener.
1795         */
1796        (new AsyncTask<Void, Void, Long>() {
1797            @Override
1798            protected Long doInBackground(Void ... arg) {
1799                synchronized (mStorageSpaceLock) {
1800                    mStorageSpaceBytes = Storage.getAvailableSpace();
1801                    return mStorageSpaceBytes;
1802                }
1803            }
1804
1805            @Override
1806            protected void onPostExecute(Long bytes) {
1807                updateStorageHint(bytes);
1808                if (callback != null) {
1809                    callback.onStorageUpdateDone(bytes);
1810                }
1811            }
1812        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1813    }
1814
1815    protected void updateStorageHint(long storageSpace) {
1816        String message = null;
1817        if (storageSpace == Storage.UNAVAILABLE) {
1818            message = getString(R.string.no_storage);
1819        } else if (storageSpace == Storage.PREPARING) {
1820            message = getString(R.string.preparing_sd);
1821        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1822            message = getString(R.string.access_sd_fail);
1823        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1824            message = getString(R.string.spaceIsLow_content);
1825        }
1826
1827        if (message != null) {
1828            Log.w(TAG, "Storage warning: " + message);
1829            if (mStorageHint == null) {
1830                mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
1831            } else {
1832                mStorageHint.setText(message);
1833            }
1834            mStorageHint.show();
1835        } else if (mStorageHint != null) {
1836            mStorageHint.cancel();
1837            mStorageHint = null;
1838       }
1839    }
1840
1841    protected void setResultEx(int resultCode) {
1842        mResultCodeForTesting = resultCode;
1843        setResult(resultCode);
1844    }
1845
1846    protected void setResultEx(int resultCode, Intent data) {
1847        mResultCodeForTesting = resultCode;
1848        mResultDataForTesting = data;
1849        setResult(resultCode, data);
1850    }
1851
1852    public int getResultCode() {
1853        return mResultCodeForTesting;
1854    }
1855
1856    public Intent getResultData() {
1857        return mResultDataForTesting;
1858    }
1859
1860    public boolean isSecureCamera() {
1861        return mSecureCamera;
1862    }
1863
1864    @Override
1865    public boolean isPaused() {
1866        return mPaused;
1867    }
1868
1869    @Override
1870    public int getPreferredChildModeIndex(int modeIndex) {
1871        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1872            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1873            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1874                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1875            }
1876        }
1877        return modeIndex;
1878    }
1879
1880    @Override
1881    public void onModeSelected(int modeIndex) {
1882        if (mCurrentModeIndex == modeIndex) {
1883            return;
1884        }
1885
1886        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1887        // Record last used camera mode for quick switching
1888        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1889                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1890            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1891                    modeIndex);
1892        }
1893
1894        closeModule(mCurrentModule);
1895
1896        // Select the correct module index from the mode switcher index.
1897        modeIndex = getPreferredChildModeIndex(modeIndex);
1898        setModuleFromModeIndex(modeIndex);
1899
1900        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1901        mCameraAppUI.addShutterListener(mCurrentModule);
1902        openModule(mCurrentModule);
1903        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1904        // Store the module index so we can use it the next time the Camera
1905        // starts up.
1906        mSettingsManager.setInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX, modeIndex);
1907    }
1908
1909    /**
1910     * Shows the settings dialog.
1911     */
1912    @Override
1913    public void onSettingsSelected() {
1914        UsageStatistics.instance().controlUsed(
1915                eventprotos.ControlEvent.ControlType.OVERALL_SETTINGS);
1916        Intent intent = new Intent(this, CameraSettingsActivity.class);
1917        startActivity(intent);
1918    }
1919
1920    /**
1921     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1922     * index an sets it as mCurrentModule.
1923     */
1924    private void setModuleFromModeIndex(int modeIndex) {
1925        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1926        if (agent == null) {
1927            return;
1928        }
1929        if (!agent.requestAppForCamera()) {
1930            mCameraController.closeCamera();
1931        }
1932        mCurrentModeIndex = agent.getModuleId();
1933        mCurrentModule = (CameraModule) agent.createModule(this);
1934    }
1935
1936    @Override
1937    public SettingsManager getSettingsManager() {
1938        return mSettingsManager;
1939    }
1940
1941    @Override
1942    public CameraServices getServices() {
1943        return (CameraServices) getApplication();
1944    }
1945
1946    public List<String> getSupportedModeNames() {
1947        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1948        List<String> supported = new ArrayList<String>();
1949
1950        for (Integer modeIndex : indices) {
1951            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1952            if (name != null && !name.equals("")) {
1953                supported.add(name);
1954            }
1955        }
1956        return supported;
1957    }
1958
1959    @Override
1960    public ButtonManager getButtonManager() {
1961        if (mButtonManager == null) {
1962            mButtonManager = new ButtonManager(this);
1963        }
1964        return mButtonManager;
1965    }
1966
1967    /**
1968     * Creates an AlertDialog appropriate for choosing whether to enable
1969     * location on the first run of the app.
1970     */
1971    public AlertDialog getFirstTimeLocationAlert() {
1972        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1973        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1974            @Override
1975            public void onCallback(Boolean locationOn) {
1976                mSettingsManager.setLocation(locationOn, mLocationManager);
1977            }
1978        });
1979        if (builder != null) {
1980            return builder.create();
1981        } else {
1982            return null;
1983        }
1984    }
1985
1986    /**
1987     * Launches an ACTION_EDIT intent for the given local data item. If
1988     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1989     * the user start either the tiny planet editor or another photo edior.
1990     *
1991     * @param data The data item to edit.
1992     */
1993    public void launchEditor(LocalData data) {
1994        Intent intent = new Intent(Intent.ACTION_EDIT)
1995                .setDataAndType(data.getUri(), data.getMimeType())
1996                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1997        try {
1998            launchActivityByIntent(intent);
1999        } catch (ActivityNotFoundException e) {
2000            launchActivityByIntent(Intent.createChooser(intent, null));
2001        }
2002    }
2003
2004    @Override
2005    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
2006        super.onCreateContextMenu(menu, v, menuInfo);
2007
2008        MenuInflater inflater = getMenuInflater();
2009        inflater.inflate(R.menu.filmstrip_context_menu, menu);
2010    }
2011
2012    @Override
2013    public boolean onContextItemSelected(MenuItem item) {
2014        switch (item.getItemId()) {
2015            case R.id.tiny_planet_editor:
2016                mMyFilmstripBottomControlListener.onTinyPlanet();
2017                return true;
2018            case R.id.photo_editor:
2019                mMyFilmstripBottomControlListener.onEdit();
2020                return true;
2021        }
2022        return false;
2023    }
2024
2025    /**
2026     * Launch the tiny planet editor.
2027     *
2028     * @param data The data must be a 360 degree stereographically mapped
2029     *            panoramic image. It will not be modified, instead a new item
2030     *            with the result will be added to the filmstrip.
2031     */
2032    public void launchTinyPlanetEditor(LocalData data) {
2033        TinyPlanetFragment fragment = new TinyPlanetFragment();
2034        Bundle bundle = new Bundle();
2035        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getUri().toString());
2036        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
2037        fragment.setArguments(bundle);
2038        fragment.show(getFragmentManager(), "tiny_planet");
2039    }
2040
2041    /**
2042     * Returns what UI mode (capture mode or filmstrip) we are in.
2043     * Returned number one of {@link com.google.common.logging.eventprotos.NavigationChange.Mode}
2044     */
2045    private int currentUserInterfaceMode() {
2046        int mode = NavigationChange.Mode.UNKNOWN_MODE;
2047        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
2048            mode = NavigationChange.Mode.PHOTO_CAPTURE;
2049        }
2050        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_video)) {
2051            mode = NavigationChange.Mode.VIDEO_CAPTURE;
2052        }
2053        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_refocus)) {
2054            mode = NavigationChange.Mode.LENS_BLUR;
2055        }
2056        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
2057            mode = NavigationChange.Mode.HDR_PLUS;
2058        }
2059        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_photosphere)) {
2060            mode = NavigationChange.Mode.PHOTO_SPHERE;
2061        }
2062        if (mCurrentModeIndex == getResources().getInteger(R.integer.camera_mode_panorama)) {
2063            mode = NavigationChange.Mode.PANORAMA;
2064        }
2065        if (mFilmstripVisible) {
2066            mode = NavigationChange.Mode.FILMSTRIP;
2067        }
2068        return mode;
2069    }
2070
2071    private void openModule(CameraModule module) {
2072        module.init(this, isSecureCamera(), isCaptureIntent());
2073        module.hardResetSettings(mSettingsManager);
2074        module.resume();
2075        UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2076                NavigationChange.InteractionCause.BUTTON);
2077        updatePreviewVisibility();
2078    }
2079
2080    private void closeModule(CameraModule module) {
2081        module.pause();
2082        mCameraAppUI.clearModuleUI();
2083    }
2084
2085    private void performDeletion() {
2086        if (!mPendingDeletion) {
2087            return;
2088        }
2089        hideUndoDeletionBar(false);
2090        mDataAdapter.executeDeletion();
2091    }
2092
2093    public void showUndoDeletionBar() {
2094        if (mPendingDeletion) {
2095            performDeletion();
2096        }
2097        Log.v(TAG, "showing undo bar");
2098        mPendingDeletion = true;
2099        if (mUndoDeletionBar == null) {
2100            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
2101                    mAboveFilmstripControlLayout, true);
2102            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
2103            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
2104            button.setOnClickListener(new View.OnClickListener() {
2105                @Override
2106                public void onClick(View view) {
2107                    mDataAdapter.undoDataRemoval();
2108                    hideUndoDeletionBar(true);
2109                }
2110            });
2111            // Setting undo bar clickable to avoid touch events going through
2112            // the bar to the buttons (eg. edit button, etc) underneath the bar.
2113            mUndoDeletionBar.setClickable(true);
2114            // When there is user interaction going on with the undo button, we
2115            // do not want to hide the undo bar.
2116            button.setOnTouchListener(new View.OnTouchListener() {
2117                @Override
2118                public boolean onTouch(View v, MotionEvent event) {
2119                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
2120                        mIsUndoingDeletion = true;
2121                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
2122                        mIsUndoingDeletion = false;
2123                    }
2124                    return false;
2125                }
2126            });
2127        }
2128        mUndoDeletionBar.setAlpha(0f);
2129        mUndoDeletionBar.setVisibility(View.VISIBLE);
2130        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
2131    }
2132
2133    private void hideUndoDeletionBar(boolean withAnimation) {
2134        Log.v(TAG, "Hiding undo deletion bar");
2135        mPendingDeletion = false;
2136        if (mUndoDeletionBar != null) {
2137            if (withAnimation) {
2138                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
2139                        .setListener(new Animator.AnimatorListener() {
2140                            @Override
2141                            public void onAnimationStart(Animator animation) {
2142                                // Do nothing.
2143                            }
2144
2145                            @Override
2146                            public void onAnimationEnd(Animator animation) {
2147                                mUndoDeletionBar.setVisibility(View.GONE);
2148                            }
2149
2150                            @Override
2151                            public void onAnimationCancel(Animator animation) {
2152                                // Do nothing.
2153                            }
2154
2155                            @Override
2156                            public void onAnimationRepeat(Animator animation) {
2157                                // Do nothing.
2158                            }
2159                        }).start();
2160            } else {
2161                mUndoDeletionBar.setVisibility(View.GONE);
2162            }
2163        }
2164    }
2165
2166    @Override
2167    public void onOrientationChanged(int orientation) {
2168        // We keep the last known orientation. So if the user first orient
2169        // the camera then point the camera to floor or sky, we still have
2170        // the correct orientation.
2171        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
2172            return;
2173        }
2174        mLastRawOrientation = orientation;
2175        if (mCurrentModule != null) {
2176            mCurrentModule.onOrientationChanged(orientation);
2177        }
2178    }
2179
2180    /**
2181     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
2182     * capture intent.
2183     *
2184     * @param enable {@code true} to enable swipe.
2185     */
2186    public void setSwipingEnabled(boolean enable) {
2187        // TODO: Bring back the functionality.
2188        if (isCaptureIntent()) {
2189            // lockPreview(true);
2190        } else {
2191            // lockPreview(!enable);
2192        }
2193    }
2194
2195    // Accessor methods for getting latency times used in performance testing
2196    public long getFirstPreviewTime() {
2197        if (mCurrentModule instanceof PhotoModule) {
2198            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
2199            if (coverHiddenTime != -1) {
2200                return coverHiddenTime - mOnCreateTime;
2201            }
2202        }
2203        return -1;
2204    }
2205
2206    public long getAutoFocusTime() {
2207        return (mCurrentModule instanceof PhotoModule) ?
2208                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
2209    }
2210
2211    public long getShutterLag() {
2212        return (mCurrentModule instanceof PhotoModule) ?
2213                ((PhotoModule) mCurrentModule).mShutterLag : -1;
2214    }
2215
2216    public long getShutterToPictureDisplayedTime() {
2217        return (mCurrentModule instanceof PhotoModule) ?
2218                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
2219    }
2220
2221    public long getPictureDisplayedToJpegCallbackTime() {
2222        return (mCurrentModule instanceof PhotoModule) ?
2223                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
2224    }
2225
2226    public long getJpegCallbackFinishTime() {
2227        return (mCurrentModule instanceof PhotoModule) ?
2228                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
2229    }
2230
2231    public long getCaptureStartTime() {
2232        return (mCurrentModule instanceof PhotoModule) ?
2233                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
2234    }
2235
2236    public boolean isRecording() {
2237        return (mCurrentModule instanceof VideoModule) ?
2238                ((VideoModule) mCurrentModule).isRecording() : false;
2239    }
2240
2241    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
2242        return mCameraController;
2243    }
2244
2245    // For debugging purposes only.
2246    public CameraModule getCurrentModule() {
2247        return mCurrentModule;
2248    }
2249
2250    @Override
2251    public void showTutorial(AbstractTutorialOverlay tutorial) {
2252        mCameraAppUI.showTutorial(tutorial, getLayoutInflater());
2253    }
2254
2255    /**
2256     * Reads the current location recording settings and passes it on to the
2257     * location manager.
2258     */
2259    public void syncLocationManagerSetting() {
2260        mSettingsManager.syncLocationManager(mLocationManager);
2261    }
2262
2263    private void keepScreenOnForAWhile() {
2264        if (mKeepScreenOn) {
2265            return;
2266        }
2267        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2268        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2269        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
2270    }
2271
2272    private void resetScreenOn() {
2273        mKeepScreenOn = false;
2274        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
2275        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2276    }
2277
2278    /**
2279     * @return {@code true} if the Gallery is launched successfully.
2280     */
2281    private boolean startGallery() {
2282        if (mGalleryIntent == null) {
2283            return false;
2284        }
2285        try {
2286            UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
2287                    NavigationChange.InteractionCause.BUTTON);
2288            Intent startGalleryIntent = new Intent(mGalleryIntent);
2289            int currentDataId = mFilmstripController.getCurrentId();
2290            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
2291            if (currentLocalData != null) {
2292                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getUri());
2293            }
2294            launchActivityByIntent(startGalleryIntent);
2295        } catch (ActivityNotFoundException e) {
2296            Log.w(TAG, "Failed to launch gallery activity, closing");
2297        }
2298        return false;
2299    }
2300
2301    private void setNfcBeamPushUriFromData(LocalData data) {
2302        final Uri uri = data.getUri();
2303        if (uri != Uri.EMPTY) {
2304            mNfcPushUris[0] = uri;
2305        } else {
2306            mNfcPushUris[0] = null;
2307        }
2308    }
2309
2310    /**
2311     * Updates the visibility of the filmstrip bottom controls and action bar.
2312     */
2313    private void updateUiByData(final int dataId) {
2314        final LocalData currentData = mDataAdapter.getLocalData(dataId);
2315        if (currentData == null) {
2316            Log.w(TAG, "Current data ID not found.");
2317            hideSessionProgress();
2318            return;
2319        }
2320        updateActionBarMenu(currentData);
2321
2322        /* Bottom controls. */
2323        updateBottomControlsByData(currentData);
2324
2325        if (isSecureCamera()) {
2326            // We cannot show buttons in secure camera since go to other
2327            // activities might create a security hole.
2328            mCameraAppUI.getFilmstripBottomControls().hideControls();
2329            return;
2330        }
2331
2332
2333        setNfcBeamPushUriFromData(currentData);
2334
2335        if (!mDataAdapter.isMetadataUpdated(dataId)) {
2336            mDataAdapter.updateMetadata(dataId);
2337        }
2338    }
2339
2340    /**
2341     * Updates the bottom controls based on the data.
2342     */
2343    private void updateBottomControlsByData(final LocalData currentData) {
2344
2345        final CameraAppUI.BottomPanel filmstripBottomPanel =
2346                mCameraAppUI.getFilmstripBottomControls();
2347        filmstripBottomPanel.showControls();
2348        filmstripBottomPanel.setEditButtonVisibility(
2349                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
2350        filmstripBottomPanel.setShareButtonVisibility(
2351                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
2352        filmstripBottomPanel.setDeleteButtonVisibility(
2353                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
2354
2355        /* Progress bar */
2356
2357        Uri contentUri = currentData.getUri();
2358        CaptureSessionManager sessionManager = getServices()
2359                .getCaptureSessionManager();
2360
2361        if (sessionManager.hasErrorMessage(contentUri)) {
2362            showProcessError(sessionManager.getErrorMesage(contentUri));
2363        } else {
2364            filmstripBottomPanel.hideProgressError();
2365            CaptureSession session = sessionManager.getSession(contentUri);
2366
2367            if (session != null) {
2368                int sessionProgress = session.getProgress();
2369
2370                if (sessionProgress < 0) {
2371                    hideSessionProgress();
2372                } else {
2373                    CharSequence progressMessage = session.getProgressMessage();
2374                    showSessionProgress(progressMessage);
2375                    updateSessionProgress(sessionProgress);
2376                }
2377            } else {
2378                hideSessionProgress();
2379            }
2380        }
2381
2382        /* View button */
2383
2384        // We need to add this to a separate DB.
2385        final int viewButtonVisibility;
2386        if (PanoramaMetadataLoader.isPanoramaAndUseViewer(currentData)) {
2387            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_PHOTO_SPHERE;
2388        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
2389            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_REFOCUS;
2390        } else {
2391            viewButtonVisibility = CameraAppUI.BottomPanel.VIEWER_NONE;
2392        }
2393
2394        filmstripBottomPanel.setTinyPlanetEnabled(
2395                PanoramaMetadataLoader.isPanorama360(currentData));
2396        filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
2397    }
2398
2399    private class PeekAnimationHandler extends Handler {
2400        private class DataAndCallback {
2401            LocalData mData;
2402            com.android.camera.util.Callback<Bitmap> mCallback;
2403
2404            public DataAndCallback(LocalData data, com.android.camera.util.Callback<Bitmap>
2405                    callback) {
2406                mData = data;
2407                mCallback = callback;
2408            }
2409        }
2410
2411        public PeekAnimationHandler(Looper looper) {
2412            super(looper);
2413        }
2414
2415        /**
2416         * Starts the animation decoding job and posts a {@code Runnable} back
2417         * when when the decoding is done.
2418         *
2419         * @param data The data item to decode the thumbnail for.
2420         * @param callback {@link com.android.camera.util.Callback} after the
2421         *                 decoding is done.
2422         */
2423        public void startDecodingJob(final LocalData data,
2424                final com.android.camera.util.Callback<Bitmap> callback) {
2425            PeekAnimationHandler.this.obtainMessage(0 /** dummy integer **/,
2426                    new DataAndCallback(data, callback)).sendToTarget();
2427        }
2428
2429        @Override
2430        public void handleMessage(Message msg) {
2431            final LocalData data = ((DataAndCallback) msg.obj).mData;
2432            final com.android.camera.util.Callback<Bitmap> callback =
2433                    ((DataAndCallback) msg.obj).mCallback;
2434            if (data == null || callback == null) {
2435                return;
2436            }
2437
2438            final Bitmap bitmap;
2439            switch (data.getLocalDataType()) {
2440                case LocalData.LOCAL_IN_PROGRESS_DATA:
2441                    byte[] jpegData = Storage.getJpegForSession(data.getUri());
2442                    if (jpegData != null) {
2443                        bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
2444                    } else {
2445                        bitmap = null;
2446                    }
2447                    break;
2448
2449                case LocalData.LOCAL_IMAGE:
2450                    FileInputStream stream;
2451                    try {
2452                        stream = new FileInputStream(data.getPath());
2453                    } catch (FileNotFoundException e) {
2454                        Log.e(TAG, "File not found:" + data.getPath());
2455                        return;
2456                    }
2457                    Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
2458                            data.getRotation(), mAboveFilmstripControlLayout.getWidth(),
2459                            mAboveFilmstripControlLayout.getMeasuredHeight());
2460                    if (data.getRotation() % 180 != 0) {
2461                        int dummy = dim.x;
2462                        dim.x = dim.y;
2463                        dim.y = dummy;
2464                    }
2465                    bitmap = LocalDataUtil
2466                            .loadImageThumbnailFromStream(stream, data.getWidth(), data.getHeight(),
2467                                    (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
2468                                    data.getRotation(), MAX_PEEK_BITMAP_PIXELS);
2469                    break;
2470
2471                case LocalData.LOCAL_VIDEO:
2472                    bitmap = LocalDataUtil.loadVideoThumbnail(data.getPath());
2473                    break;
2474
2475                default:
2476                    bitmap = null;
2477                    break;
2478            }
2479
2480            if (bitmap == null) {
2481                return;
2482            }
2483
2484            mMainHandler.post(new Runnable() {
2485                @Override
2486                public void run() {
2487                    callback.onCallback(bitmap);
2488                }
2489            });
2490        }
2491    }
2492
2493    private void showDetailsDialog(int dataId) {
2494        final LocalData data = mDataAdapter.getLocalData(dataId);
2495        if (data == null) {
2496            return;
2497        }
2498        MediaDetails details = data.getMediaDetails(getAndroidContext());
2499        if (details == null) {
2500            return;
2501        }
2502        Dialog detailDialog = DetailsDialog.create(CameraActivity.this, details);
2503        detailDialog.show();
2504        UsageStatistics.instance().mediaInteraction(
2505                fileNameFromDataID(dataId), MediaInteraction.InteractionType.DETAILS,
2506                NavigationChange.InteractionCause.BUTTON);
2507    }
2508
2509    /**
2510     * Show or hide action bar items depending on current data type.
2511     */
2512    private void updateActionBarMenu(LocalData data) {
2513        if (mActionBarMenu == null) {
2514            return;
2515        }
2516
2517        MenuItem detailsMenuItem = mActionBarMenu.findItem(R.id.action_details);
2518        if (detailsMenuItem == null) {
2519            return;
2520        }
2521
2522        int type = data.getLocalDataType();
2523        boolean showDetails = (type == LocalData.LOCAL_IMAGE) || (type == LocalData.LOCAL_VIDEO);
2524        detailsMenuItem.setVisible(showDetails);
2525    }
2526}
2527