CameraActivity.java revision e346fb291bd6209b0167d110e15f58576568f99c
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.content.ActivityNotFoundException;
25import android.content.BroadcastReceiver;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.pm.ActivityInfo;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.graphics.Bitmap;
35import android.graphics.Matrix;
36import android.graphics.SurfaceTexture;
37import android.graphics.drawable.ColorDrawable;
38import android.graphics.drawable.Drawable;
39import android.net.Uri;
40import android.nfc.NfcAdapter;
41import android.nfc.NfcAdapter.CreateBeamUrisCallback;
42import android.nfc.NfcEvent;
43import android.os.Build;
44import android.os.Bundle;
45import android.os.Handler;
46import android.os.Looper;
47import android.os.Message;
48import android.preference.PreferenceManager;
49import android.provider.MediaStore;
50import android.provider.Settings;
51import android.util.CameraPerformanceTracker;
52import android.util.Log;
53import android.view.ContextMenu;
54import android.view.ContextMenu.ContextMenuInfo;
55import android.view.KeyEvent;
56import android.view.MenuInflater;
57import android.view.MenuItem;
58import android.view.MotionEvent;
59import android.view.View;
60import android.view.ViewGroup;
61import android.view.Window;
62import android.view.WindowManager;
63import android.widget.FrameLayout;
64import android.widget.ImageView;
65import android.widget.ProgressBar;
66import android.widget.ShareActionProvider;
67import android.widget.TextView;
68
69import com.android.camera.app.AppController;
70import com.android.camera.app.CameraAppUI;
71import com.android.camera.app.CameraController;
72import com.android.camera.app.CameraManager;
73import com.android.camera.app.CameraManagerFactory;
74import com.android.camera.app.CameraProvider;
75import com.android.camera.app.CameraServices;
76import com.android.camera.app.LocationManager;
77import com.android.camera.app.ModuleManagerImpl;
78import com.android.camera.app.OrientationManager;
79import com.android.camera.app.OrientationManagerImpl;
80import com.android.camera.data.CameraDataAdapter;
81import com.android.camera.data.FixedLastDataAdapter;
82import com.android.camera.data.InProgressDataWrapper;
83import com.android.camera.data.LocalData;
84import com.android.camera.data.LocalDataAdapter;
85import com.android.camera.data.LocalMediaObserver;
86import com.android.camera.data.PanoramaMetadataLoader;
87import com.android.camera.data.RgbzMetadataLoader;
88import com.android.camera.data.SimpleViewData;
89import com.android.camera.filmstrip.FilmstripContentPanel;
90import com.android.camera.filmstrip.FilmstripController;
91import com.android.camera.hardware.HardwareSpec;
92import com.android.camera.hardware.HardwareSpecImpl;
93import com.android.camera.module.ModuleController;
94import com.android.camera.module.ModulesInfo;
95import com.android.camera.session.CaptureSessionManager;
96import com.android.camera.session.CaptureSessionManager.SessionListener;
97import com.android.camera.session.PlaceholderManager;
98import com.android.camera.settings.CameraSettingsActivity;
99import com.android.camera.settings.SettingsManager;
100import com.android.camera.settings.SettingsManager.SettingsCapabilities;
101import com.android.camera.settings.SettingsUtil;
102import com.android.camera.tinyplanet.TinyPlanetFragment;
103import com.android.camera.ui.MainActivityLayout;
104import com.android.camera.ui.ModeListView;
105import com.android.camera.ui.PreviewStatusListener;
106import com.android.camera.util.ApiHelper;
107import com.android.camera.util.Callback;
108import com.android.camera.util.CameraUtil;
109import com.android.camera.util.FeedbackHelper;
110import com.android.camera.util.GalleryHelper;
111import com.android.camera.util.GcamHelper;
112import com.android.camera.util.IntentHelper;
113import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
114import com.android.camera.util.ReleaseDialogHelper;
115import com.android.camera.util.UsageStatistics;
116import com.android.camera.widget.FilmstripView;
117import com.android.camera2.R;
118import com.google.common.logging.eventprotos;
119import com.google.common.logging.eventprotos.CameraEvent.InteractionCause;
120import com.google.common.logging.eventprotos.NavigationChange;
121
122import java.io.File;
123import java.lang.ref.WeakReference;
124import java.util.ArrayList;
125import java.util.List;
126
127public class CameraActivity extends Activity
128        implements AppController, CameraManager.CameraOpenCallback,
129        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
130        OrientationManager.OnOrientationChangeListener {
131
132    private static final String TAG = "CameraActivity";
133
134    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
135            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
136    public static final String ACTION_IMAGE_CAPTURE_SECURE =
137            "android.media.action.IMAGE_CAPTURE_SECURE";
138
139    // The intent extra for camera from secure lock screen. True if the gallery
140    // should only show newly captured pictures. sSecureAlbumId does not
141    // increment. This is used when switching between camera, camcorder, and
142    // panorama. If the extra is not set, it is in the normal camera mode.
143    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
144
145    /**
146     * Request code from an activity we started that indicated that we do not
147     * want to reset the view to the preview in onResume.
148     */
149    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
150
151    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
152
153    private static final int MSG_HIDE_ACTION_BAR = 1;
154    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
155    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
156
157    /** Should be used wherever a context is needed. */
158    private Context mAppContext;
159
160    /**
161     * Whether onResume should reset the view to the preview.
162     */
163    private boolean mResetToPreviewOnResume = true;
164
165    /**
166     * This data adapter is used by FilmStripView.
167     */
168    private LocalDataAdapter mDataAdapter;
169
170    /**
171     * TODO: This should be moved to the app level.
172     */
173    private SettingsManager mSettingsManager;
174
175    private ModeListView mModeListView;
176    private int mCurrentModeIndex;
177    private CameraModule mCurrentModule;
178    private ModuleManagerImpl mModuleManager;
179    private FrameLayout mAboveFilmstripControlLayout;
180    private FilmstripController mFilmstripController;
181    private boolean mFilmstripVisible;
182    private TextView mBottomProgressText;
183    private ProgressBar mBottomProgressBar;
184    private View mSessionProgressPanel;
185    private int mResultCodeForTesting;
186    private Intent mResultDataForTesting;
187    private OnScreenHint mStorageHint;
188    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
189    private boolean mAutoRotateScreen;
190    private boolean mSecureCamera;
191    private int mLastRawOrientation;
192    private OrientationManagerImpl mOrientationManager;
193    private LocationManager mLocationManager;
194    private ButtonManager mButtonManager;
195    private Handler mMainHandler;
196    private PanoramaViewHelper mPanoramaViewHelper;
197    private ActionBar mActionBar;
198    private ViewGroup mUndoDeletionBar;
199    private boolean mIsUndoingDeletion = false;
200
201    private final Uri[] mNfcPushUris = new Uri[1];
202
203    private LocalMediaObserver mLocalImagesObserver;
204    private LocalMediaObserver mLocalVideosObserver;
205
206    private boolean mPendingDeletion = false;
207
208    private CameraController mCameraController;
209    private boolean mPaused;
210    private boolean mUpAsGallery;
211    private CameraAppUI mCameraAppUI;
212
213    private FeedbackHelper mFeedbackHelper;
214
215    private Intent mGalleryIntent;
216    private long mOnCreateTime;
217
218    @Override
219    public CameraAppUI getCameraAppUI() {
220        return mCameraAppUI;
221    }
222
223    // close activity when screen turns off
224    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
225        @Override
226        public void onReceive(Context context, Intent intent) {
227            finish();
228        }
229    };
230
231    /**
232     * Whether the screen is kept turned on.
233     */
234    private boolean mKeepScreenOn;
235    private int mLastLayoutOrientation;
236    private final CameraAppUI.BottomControls.Listener mMyFilmstripBottomControlListener =
237            new CameraAppUI.BottomControls.Listener() {
238
239                /**
240                 * If the current photo is a photo sphere, this will launch the
241                 * Photo Sphere panorama viewer.
242                 */
243                @Override
244                public void onExternalViewer() {
245                    if (mPanoramaViewHelper == null) {
246                        return;
247                    }
248                    final LocalData data = getCurrentLocalData();
249                    if (data == null) {
250                        return;
251                    }
252                    final Uri contentUri = data.getContentUri();
253                    if (contentUri == Uri.EMPTY) {
254                        return;
255                    }
256
257                    if (PanoramaMetadataLoader.isPanorama(data)) {
258                        mPanoramaViewHelper.showPanorama(contentUri);
259                    } else if (RgbzMetadataLoader.hasRGBZData(data)) {
260                        mPanoramaViewHelper.showRgbz(contentUri);
261                    }
262                }
263
264                @Override
265                public void onEdit() {
266                    LocalData data = getCurrentLocalData();
267                    if (data == null) {
268                        return;
269                    }
270                    launchEditor(data);
271                }
272
273                @Override
274                public void onTinyPlanet() {
275                    LocalData data = getCurrentLocalData();
276                    if (data == null) {
277                        return;
278                    }
279                    launchTinyPlanetEditor(data);
280                }
281
282                @Override
283                public void onDelete() {
284                    final int currentDataId = getCurrentDataId();
285                    UsageStatistics.photoInteraction(
286                            UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
287                            eventprotos.CameraEvent.InteractionType.DELETE,
288                            InteractionCause.BUTTON);
289                    removeData(currentDataId);
290                }
291
292                @Override
293                public void onShare() {
294                    final LocalData data = getCurrentLocalData();
295                    Intent shareIntent = getShareIntentByData(data);
296                    if (shareIntent != null) {
297                        try {
298                            launchActivityByIntent(shareIntent);
299                            mCameraAppUI.getFilmstripBottomControls().setShareEnabled(false);
300                        } catch (ActivityNotFoundException ex) {
301                            // Nothing.
302                        }
303                    }
304                }
305
306                private int getCurrentDataId() {
307                    return mFilmstripController.getCurrentId();
308                }
309
310                private LocalData getCurrentLocalData() {
311                    return mDataAdapter.getLocalData(getCurrentDataId());
312                }
313
314                /**
315                 * Sets up the share intent and NFC properly according to the
316                 * data.
317                 *
318                 * @param data The data to be shared.
319                 */
320                private Intent getShareIntentByData(final LocalData data) {
321                    Intent intent = null;
322                    final Uri contentUri = data.getContentUri();
323                    if (PanoramaMetadataLoader.isPanorama360(data) &&
324                            data.getContentUri() != Uri.EMPTY) {
325                        intent = new Intent(Intent.ACTION_SEND);
326                        intent.setType("application/vnd.google.panorama360+jpg");
327                        intent.putExtra(Intent.EXTRA_STREAM, contentUri);
328                    } else if (data.isDataActionSupported(LocalData.DATA_ACTION_SHARE)) {
329                        final String mimeType = data.getMimeType();
330                        intent = getShareIntentFromType(mimeType);
331                        if (intent != null) {
332                            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
333                            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
334                        }
335                    }
336                    return intent;
337                }
338
339                /**
340                 * Get the share intent according to the mimeType
341                 *
342                 * @param mimeType The mimeType of current data.
343                 * @return the video/image's ShareIntent or null if mimeType is
344                 *         invalid.
345                 */
346                private Intent getShareIntentFromType(String mimeType) {
347                    // Lazily create the intent object.
348                    Intent intent = new Intent(Intent.ACTION_SEND);
349                    if (mimeType.startsWith("video/")) {
350                        intent.setType("video/*");
351                    } else {
352                        if (mimeType.startsWith("image/")) {
353                            intent.setType("image/*");
354                        } else {
355                            Log.w(TAG, "unsupported mimeType " + mimeType);
356                        }
357                    }
358                    return intent;
359                }
360            };
361
362    private ComboPreferences mPreferences;
363    private ContentResolver mContentResolver;
364
365    @Override
366    public void onCameraOpened(CameraManager.CameraProxy camera) {
367        /**
368         * The current UI requires that the flash option visibility in front-facing
369         * camera be
370         *   * disabled if back facing camera supports flash
371         *   * hidden if back facing camera does not support flash
372         * We save whether back facing camera supports flash because we cannot get
373         * this in front facing camera without a camera switch.
374         *
375         * If this preference is cleared, we also need to clear the camera facing
376         * setting so we default to opening the camera in back facing camera, and
377         * can save this flash support value again.
378         */
379        if (!mSettingsManager.isSet(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA)) {
380            HardwareSpec hardware = new HardwareSpecImpl(camera.getParameters());
381            mSettingsManager.setBoolean(SettingsManager.SETTING_FLASH_SUPPORTED_BACK_CAMERA,
382                hardware.isFlashSupported());
383        }
384
385        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
386            // We shouldn't be here. Just close the camera and leave.
387            camera.release(false);
388            throw new IllegalStateException("Camera opened but the module shouldn't be " +
389                    "requesting");
390        }
391        if (mCurrentModule != null) {
392            SettingsCapabilities capabilities =
393                    SettingsUtil.getSettingsCapabilities(camera);
394            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
395            mCurrentModule.onCameraAvailable(camera);
396        }
397        mCameraAppUI.onChangeCamera();
398    }
399
400    @Override
401    public void onCameraDisabled(int cameraId) {
402        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY);
403
404        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
405    }
406
407    @Override
408    public void onDeviceOpenFailure(int cameraId) {
409        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.OPEN_FAILURE);
410
411        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
412    }
413
414    @Override
415    public void onReconnectionFailure(CameraManager mgr) {
416        UsageStatistics.cameraFailure(eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE);
417
418        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
419    }
420
421    private static class MainHandler extends Handler {
422        final WeakReference<CameraActivity> mActivity;
423
424        public MainHandler(CameraActivity activity, Looper looper) {
425            super(looper);
426            mActivity = new WeakReference<CameraActivity>(activity);
427        }
428
429        @Override
430        public void handleMessage(Message msg) {
431            CameraActivity activity = mActivity.get();
432            if (activity == null) {
433                return;
434            }
435            switch (msg.what) {
436                case MSG_HIDE_ACTION_BAR: {
437                    removeMessages(MSG_HIDE_ACTION_BAR);
438                    activity.setFilmstripUiVisibility(false);
439                    break;
440                }
441
442                case MSG_CLEAR_SCREEN_ON_FLAG: {
443                    if (!activity.mPaused) {
444                        activity.getWindow().clearFlags(
445                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
446                    }
447                    break;
448                }
449            }
450        }
451    }
452
453    private String fileNameFromDataID(int dataID) {
454        final LocalData localData = mDataAdapter.getLocalData(dataID);
455
456        File localFile = new File(localData.getPath());
457        return localFile.getName();
458    }
459
460    private final FilmstripContentPanel.Listener mFilmstripListener =
461            new FilmstripContentPanel.Listener() {
462
463                @Override
464                public void onSwipeOut() {
465                    UsageStatistics.changeScreen(eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
466                            eventprotos.CameraEvent.InteractionCause.SWIPE_RIGHT);
467                }
468
469                @Override
470                public void onFilmstripHidden() {
471                    mFilmstripVisible = false;
472                    CameraActivity.this.setFilmstripUiVisibility(false);
473                    // When the user hide the filmstrip (either swipe out or
474                    // tap on back key) we move to the first item so next time
475                    // when the user swipe in the filmstrip, the most recent
476                    // one is shown.
477                    mFilmstripController.goToFirstItem();
478                    if (mCurrentModule != null) {
479                        mCurrentModule.onPreviewVisibilityChanged(true);
480                    }
481                }
482
483                @Override
484                public void onFilmstripShown() {
485                    mFilmstripVisible = true;
486                    updateUiByData(mFilmstripController.getCurrentId());
487                    if (mCurrentModule != null) {
488                        mCurrentModule.onPreviewVisibilityChanged(false);
489                    }
490                }
491
492                @Override
493                public void onDataPromoted(int dataID) {
494                    UsageStatistics.photoInteraction(
495                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
496                            eventprotos.CameraEvent.InteractionType.DELETE,
497                            InteractionCause.SWIPE_UP);
498
499                    removeData(dataID);
500                }
501
502                @Override
503                public void onDataDemoted(int dataID) {
504                    UsageStatistics.photoInteraction(
505                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)),
506                            eventprotos.CameraEvent.InteractionType.DELETE,
507                            InteractionCause.SWIPE_DOWN);
508
509                    removeData(dataID);
510                }
511
512                @Override
513                public void onEnterFullScreenUiShown(int dataId) {
514                    if (mFilmstripVisible) {
515                        CameraActivity.this.setFilmstripUiVisibility(true);
516                    }
517                }
518
519                @Override
520                public void onLeaveFullScreenUiShown(int dataId) {
521                    // Do nothing.
522                }
523
524                @Override
525                public void onEnterFullScreenUiHidden(int dataId) {
526                    if (mFilmstripVisible) {
527                        CameraActivity.this.setFilmstripUiVisibility(false);
528                    }
529                }
530
531                @Override
532                public void onLeaveFullScreenUiHidden(int dataId) {
533                    // Do nothing.
534                }
535
536                @Override
537                public void onEnterFilmstrip(int dataId) {
538                    if (mGalleryIntent != null) {
539                        mActionBar.setDisplayUseLogoEnabled(true);
540                        mUpAsGallery = true;
541                    }
542                    if (mFilmstripVisible) {
543                        CameraActivity.this.setFilmstripUiVisibility(true);
544                    }
545                }
546
547                @Override
548                public void onLeaveFilmstrip(int dataId) {
549                    if (mGalleryIntent != null) {
550                        mActionBar.setDisplayUseLogoEnabled(false);
551                        mUpAsGallery = false;
552                    }
553                }
554
555                @Override
556                public void onDataReloaded() {
557                    if (!mFilmstripVisible) {
558                        return;
559                    }
560                    updateUiByData(mFilmstripController.getCurrentId());
561                }
562
563                @Override
564                public void onDataUpdated(int dataId) {
565                    if (!mFilmstripVisible) {
566                        return;
567                    }
568                    updateUiByData(mFilmstripController.getCurrentId());
569                }
570
571                @Override
572                public void onEnterZoomView(int dataID) {
573                    if (mFilmstripVisible) {
574                        CameraActivity.this.setFilmstripUiVisibility(false);
575                    }
576                }
577
578                @Override
579                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
580                    if (!mFilmstripVisible) {
581                        return;
582                    }
583                    // TODO: This callback is UI event callback, should always
584                    // happen on UI thread. Find the reason for this
585                    // runOnUiThread() and fix it.
586                    runOnUiThread(new Runnable() {
587                        @Override
588                        public void run() {
589                            updateUiByData(newDataId);
590                        }
591                    });
592                }
593            };
594
595    private final LocalDataAdapter.LocalDataListener mLocalDataListener =
596            new LocalDataAdapter.LocalDataListener() {
597                @Override
598                public void onMetadataUpdated(List<Integer> updatedData) {
599                    int currentDataId = mFilmstripController.getCurrentId();
600                    for (Integer dataId : updatedData) {
601                        if (dataId == currentDataId) {
602                            updateBottomControlsByData(mDataAdapter.getLocalData(dataId));
603                        }
604                    }
605                }
606            };
607
608    public void gotoGallery() {
609        UsageStatistics.changeScreen(NavigationChange.Mode.FILMSTRIP,
610                InteractionCause.BUTTON);
611
612        mFilmstripController.goToNextItem();
613    }
614
615    /**
616     * If 'visible' is false, this hides the action bar and switches the
617     * filmstrip UI to lights-out mode.
618     *
619     * @param visible is false, this hides the action bar and switches the
620     *            filmstrip UI to lights-out mode.
621     */
622    // TODO: This should not be called outside of the activity.
623    public void setFilmstripUiVisibility(boolean visible) {
624        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
625
626        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
627        int newSystemUIVisibility = (visible ? View.SYSTEM_UI_FLAG_VISIBLE
628                : View.SYSTEM_UI_FLAG_FULLSCREEN);
629        if (newSystemUIVisibility != currentSystemUIVisibility) {
630            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
631        }
632
633        boolean currentActionBarVisibility = mActionBar.isShowing();
634        mCameraAppUI.getFilmstripBottomControls().setVisible(visible);
635        if (visible != currentActionBarVisibility) {
636            if (visible) {
637                mActionBar.show();
638            } else {
639                mActionBar.hide();
640            }
641        }
642    }
643
644    private void hideSessionProgress() {
645        mSessionProgressPanel.setVisibility(View.GONE);
646    }
647
648    private void showSessionProgress(CharSequence message) {
649        mBottomProgressText.setText(message);
650        mSessionProgressPanel.setVisibility(View.VISIBLE);
651    }
652
653    private void updateSessionProgress(int progress) {
654        mBottomProgressBar.setProgress(progress);
655    }
656
657    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
658    private void setupNfcBeamPush() {
659        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mAppContext);
660        if (adapter == null) {
661            return;
662        }
663
664        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
665            // Disable beaming
666            adapter.setNdefPushMessage(null, CameraActivity.this);
667            return;
668        }
669
670        adapter.setBeamPushUris(null, CameraActivity.this);
671        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
672            @Override
673            public Uri[] createBeamUris(NfcEvent event) {
674                return mNfcPushUris;
675            }
676        }, CameraActivity.this);
677    }
678
679    @Override
680    public void onMenuVisibilityChanged(boolean isVisible) {
681        // TODO: Remove this or bring back the original implementation: cancel
682        // auto-hide actionbar.
683    }
684
685    @Override
686    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
687        int currentDataId = mFilmstripController.getCurrentId();
688        if (currentDataId < 0) {
689            return false;
690        }
691        UsageStatistics.photoInteraction(
692                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)),
693                eventprotos.CameraEvent.InteractionType.SHARE,
694                InteractionCause.BUTTON);
695        // TODO add intent.getComponent().getPackageName()
696        return true;
697    }
698
699    // Note: All callbacks come back on the main thread.
700    private final SessionListener mSessionListener =
701            new SessionListener() {
702                @Override
703                public void onSessionQueued(final Uri uri) {
704                    notifyNewMedia(uri);
705                    int dataID = mDataAdapter.findDataByContentUri(uri);
706                    if (dataID != -1) {
707                        // Don't allow special UI actions (swipe to
708                        // delete, for example) on in-progress data.
709                        LocalData d = mDataAdapter.getLocalData(dataID);
710                        InProgressDataWrapper newData = new InProgressDataWrapper(d);
711                        mDataAdapter.updateData(dataID, newData);
712                    }
713                }
714
715                @Override
716                public void onSessionDone(final Uri uri) {
717                    Log.v(TAG, "onSessionDone:" + uri);
718                    int doneID = mDataAdapter.findDataByContentUri(uri);
719                    int currentDataId = mFilmstripController.getCurrentId();
720
721                    if (currentDataId == doneID) {
722                        hideSessionProgress();
723                        updateSessionProgress(0);
724                    }
725                    mDataAdapter.refresh(uri, /* isInProgress */false);
726                }
727
728                @Override
729                public void onSessionProgress(final Uri uri, final int progress) {
730                    if (progress < 0) {
731                        // Do nothing, there is no task for this URI.
732                        return;
733                    }
734                    int currentDataId = mFilmstripController.getCurrentId();
735                    if (currentDataId == -1) {
736                        return;
737                    }
738                    if (uri.equals(
739                            mDataAdapter.getLocalData(currentDataId).getContentUri())) {
740                        updateSessionProgress(progress);
741                    }
742                }
743
744                @Override
745                public void onSessionUpdated(Uri uri) {
746                    mDataAdapter.refresh(uri, /* isInProgress */true);
747                }
748            };
749
750    @Override
751    public Context getAndroidContext() {
752        return mAppContext;
753    }
754
755    @Override
756    public void launchActivityByIntent(Intent intent) {
757        startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
758    }
759
760    @Override
761    public int getCurrentModuleIndex() {
762        return mCurrentModeIndex;
763    }
764
765    @Override
766    public ModuleController getCurrentModuleController() {
767        return mCurrentModule;
768    }
769
770    @Override
771    public int getQuickSwitchToModuleId(int currentModuleIndex) {
772        return mModuleManager.getQuickSwitchToModuleId(currentModuleIndex, mSettingsManager,
773                mAppContext);
774    }
775
776    @Override
777    public SurfaceTexture getPreviewBuffer() {
778        // TODO: implement this
779        return null;
780    }
781
782    @Override
783    public void onPreviewReadyToStart() {
784        mCameraAppUI.onPreviewReadyToStart();
785    }
786
787    @Override
788    public void onPreviewStarted() {
789        mCameraAppUI.onPreviewStarted();
790    }
791
792    @Override
793    public void addPreviewAreaSizeChangedListener(
794            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
795        mCameraAppUI.addPreviewAreaSizeChangedListener(listener);
796    }
797
798    @Override
799    public void removePreviewAreaSizeChangedListener(
800            PreviewStatusListener.PreviewAreaSizeChangedListener listener) {
801        mCameraAppUI.removePreviewAreaSizeChangedListener(listener);
802    }
803
804    @Override
805    public void setupOneShotPreviewListener() {
806        mCameraController.setOneShotPreviewCallback(mMainHandler,
807                new CameraManager.CameraPreviewDataCallback() {
808                    @Override
809                    public void onPreviewFrame(byte[] data, CameraManager.CameraProxy camera) {
810                        mCurrentModule.onPreviewInitialDataReceived();
811                        mCameraAppUI.onNewPreviewFrame();
812                    }
813                });
814    }
815
816    @Override
817    public void updatePreviewAspectRatio(float aspectRatio) {
818        mCameraAppUI.updatePreviewAspectRatio(aspectRatio);
819    }
820
821    @Override
822    public boolean shouldShowShimmy() {
823        int remainingTimes = mSettingsManager.getInt(
824                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX);
825        return remainingTimes > 0;
826    }
827
828    @Override
829    public void decrementShimmyPlayTimes() {
830        int remainingTimes = mSettingsManager.getInt(
831                SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX) - 1;
832        if (remainingTimes >= 0) {
833            mSettingsManager.setInt(SettingsManager.SETTING_SHIMMY_REMAINING_PLAY_TIMES_INDEX,
834                    remainingTimes);
835        }
836    }
837
838    @Override
839    public void updatePreviewTransform(Matrix matrix) {
840        mCameraAppUI.updatePreviewTransform(matrix);
841    }
842
843    @Override
844    public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
845        mCameraAppUI.setPreviewStatusListener(previewStatusListener);
846    }
847
848    @Override
849    public FrameLayout getModuleLayoutRoot() {
850        return mCameraAppUI.getModuleRootView();
851    }
852
853    @Override
854    public void setShutterEventsListener(ShutterEventsListener listener) {
855        // TODO: implement this
856    }
857
858    @Override
859    public void setShutterEnabled(boolean enabled) {
860        // TODO: implement this
861    }
862
863    @Override
864    public boolean isShutterEnabled() {
865        // TODO: implement this
866        return false;
867    }
868
869    @Override
870    public void startPreCaptureAnimation() {
871        mCameraAppUI.startPreCaptureAnimation();
872    }
873
874    @Override
875    public void cancelPreCaptureAnimation() {
876        // TODO: implement this
877    }
878
879    @Override
880    public void startPostCaptureAnimation() {
881        // TODO: implement this
882    }
883
884    @Override
885    public void startPostCaptureAnimation(Bitmap thumbnail) {
886        // TODO: implement this
887    }
888
889    @Override
890    public void cancelPostCaptureAnimation() {
891        // TODO: implement this
892    }
893
894    @Override
895    public OrientationManager getOrientationManager() {
896        return mOrientationManager;
897    }
898
899    @Override
900    public LocationManager getLocationManager() {
901        return mLocationManager;
902    }
903
904    @Override
905    public void lockOrientation() {
906        mOrientationManager.lockOrientation();
907    }
908
909    @Override
910    public void unlockOrientation() {
911        mOrientationManager.unlockOrientation();
912    }
913
914    @Override
915    public void notifyNewMedia(Uri uri) {
916        ContentResolver cr = getContentResolver();
917        String mimeType = cr.getType(uri);
918        if (mimeType.startsWith("video/")) {
919            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
920            mDataAdapter.addNewVideo(uri);
921        } else if (mimeType.startsWith("image/")) {
922            CameraUtil.broadcastNewPicture(mAppContext, uri);
923            mDataAdapter.addNewPhoto(uri);
924        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
925            mDataAdapter.addNewPhoto(uri);
926        } else {
927            android.util.Log.w(TAG, "Unknown new media with MIME type:"
928                    + mimeType + ", uri:" + uri);
929        }
930    }
931
932    @Override
933    public void enableKeepScreenOn(boolean enabled) {
934        if (mPaused) {
935            return;
936        }
937
938        mKeepScreenOn = enabled;
939        if (mKeepScreenOn) {
940            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
941            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
942        } else {
943            keepScreenOnForAWhile();
944        }
945    }
946
947    @Override
948    public CameraProvider getCameraProvider() {
949        return mCameraController;
950    }
951
952    private void removeData(int dataID) {
953        mDataAdapter.removeData(dataID);
954        if (mDataAdapter.getTotalNumber() > 1) {
955            showUndoDeletionBar();
956        } else {
957            // If camera preview is the only view left in filmstrip,
958            // no need to show undo bar.
959            mPendingDeletion = true;
960            performDeletion();
961            if (mFilmstripVisible) {
962                mCameraAppUI.getFilmstripContentPanel().animateHide();
963            }
964        }
965    }
966
967    @Override
968    public boolean onOptionsItemSelected(MenuItem item) {
969        // Handle presses on the action bar items
970        switch (item.getItemId()) {
971            case android.R.id.home:
972                if (mFilmstripVisible && mUpAsGallery && startGallery()) {
973                    return true;
974                }
975                onBackPressed();
976                return true;
977            default:
978                return super.onOptionsItemSelected(item);
979        }
980    }
981
982    private boolean isCaptureIntent() {
983        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
984                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
985                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
986            return true;
987        } else {
988            return false;
989        }
990    }
991
992    @Override
993    public void onCreate(Bundle state) {
994        super.onCreate(state);
995        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
996        mOnCreateTime = System.currentTimeMillis();
997        mAppContext = getApplicationContext();
998        GcamHelper.init(getContentResolver());
999
1000        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1001        setContentView(R.layout.activity_main);
1002        mActionBar = getActionBar();
1003        mActionBar.addOnMenuVisibilityListener(this);
1004        mMainHandler = new MainHandler(this, getMainLooper());
1005        mCameraController =
1006                new CameraController(mAppContext, this, mMainHandler,
1007                        CameraManagerFactory.getAndroidCameraManager());
1008        mPreferences = new ComboPreferences(mAppContext);
1009        mContentResolver = this.getContentResolver();
1010
1011        mSettingsManager = new SettingsManager(mAppContext, this,
1012                mCameraController.getNumberOfCameras());
1013
1014        // Remove this after we get rid of ComboPreferences.
1015        int cameraId = Integer.parseInt(mSettingsManager.get(SettingsManager.SETTING_CAMERA_ID));
1016        mPreferences.setLocalId(mAppContext, cameraId);
1017        CameraSettings.upgradeGlobalPreferences(mPreferences,
1018                mCameraController.getNumberOfCameras());
1019        // TODO: Try to move all the resources allocation to happen as soon as
1020        // possible so we can call module.init() at the earliest time.
1021        mModuleManager = new ModuleManagerImpl();
1022        ModulesInfo.setupModules(mAppContext, mModuleManager);
1023
1024        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1025        mModeListView.init(mModuleManager.getSupportedModeIndexList());
1026        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1027            setRotationAnimation();
1028        }
1029
1030        // Check if this is in the secure camera mode.
1031        Intent intent = getIntent();
1032        String action = intent.getAction();
1033        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1034                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1035            mSecureCamera = true;
1036        } else {
1037            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1038        }
1039
1040        if (mSecureCamera) {
1041            // Foreground event caused by lock screen startup.
1042            // It is necessary to log this in onCreate, to avoid the
1043            // onResume->onPause->onResume sequence.
1044            UsageStatistics.foregrounded(
1045                    eventprotos.ForegroundEvent.ForegroundSource.LOCK_SCREEN);
1046
1047            // Change the window flags so that secure camera can show when
1048            // locked
1049            Window win = getWindow();
1050            WindowManager.LayoutParams params = win.getAttributes();
1051            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1052            win.setAttributes(params);
1053
1054            // Filter for screen off so that we can finish secure camera
1055            // activity
1056            // when screen is off.
1057            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1058            registerReceiver(mScreenOffReceiver, filter);
1059        }
1060        mCameraAppUI = new CameraAppUI(this,
1061                (MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
1062
1063        mCameraAppUI.setFilmstripBottomControlsListener(mMyFilmstripBottomControlListener);
1064
1065        mAboveFilmstripControlLayout =
1066                (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
1067
1068        // Add the session listener so we can track the session progress
1069        // updates.
1070        getServices().getCaptureSessionManager().addSessionListener(mSessionListener);
1071        mSessionProgressPanel = findViewById(R.id.pano_session_progress_panel);
1072        mBottomProgressBar = (ProgressBar) findViewById(R.id.pano_session_progress_bar);
1073        mBottomProgressText = (TextView) findViewById(R.id.pano_session_progress_text);
1074        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1075        mFilmstripController.setImageGap(
1076                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1077        mPanoramaViewHelper = new PanoramaViewHelper(this);
1078        mPanoramaViewHelper.onCreate();
1079        // Set up the camera preview first so the preview shows up ASAP.
1080        mDataAdapter = new CameraDataAdapter(mAppContext,
1081                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
1082        mDataAdapter.setLocalDataListener(mLocalDataListener);
1083
1084        mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
1085
1086        mLocationManager = new LocationManager(mAppContext);
1087
1088        int modeIndex = -1;
1089        int photoIndex = getResources().getInteger(R.integer.camera_mode_photo);
1090        int videoIndex = getResources().getInteger(R.integer.camera_mode_video);
1091        int gcamIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1092        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1093                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1094            modeIndex = videoIndex;
1095        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1096                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1097                        .getAction())) {
1098            modeIndex = photoIndex;
1099            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1100                        == gcamIndex && GcamHelper.hasGcamCapture()) {
1101                modeIndex = gcamIndex;
1102            }
1103        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1104                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1105            modeIndex = photoIndex;
1106        } else {
1107            // If the activity has not been started using an explicit intent,
1108            // read the module index from the last time the user changed modes
1109            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1110            if ((modeIndex == gcamIndex &&
1111                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1112                modeIndex = photoIndex;
1113            }
1114        }
1115
1116        mOrientationManager = new OrientationManagerImpl(this);
1117        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1118
1119        setModuleFromModeIndex(modeIndex);
1120        mCameraAppUI.prepareModuleUI();
1121        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1122
1123        if (!mSecureCamera) {
1124            mFilmstripController.setDataAdapter(mDataAdapter);
1125            if (!isCaptureIntent()) {
1126                mDataAdapter.requestLoad();
1127            }
1128        } else {
1129            // Put a lock placeholder as the last image by setting its date to
1130            // 0.
1131            ImageView v = (ImageView) getLayoutInflater().inflate(
1132                    R.layout.secure_album_placeholder, null);
1133            v.setOnClickListener(new View.OnClickListener() {
1134                @Override
1135                public void onClick(View view) {
1136                    UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY,
1137                            InteractionCause.BUTTON);
1138                    startGallery();
1139                    finish();
1140                }
1141            });
1142            mDataAdapter = new FixedLastDataAdapter(
1143                    mAppContext,
1144                    mDataAdapter,
1145                    new SimpleViewData(
1146                            v,
1147                            v.getDrawable().getIntrinsicWidth(),
1148                            v.getDrawable().getIntrinsicHeight(),
1149                            0, 0));
1150            // Flush out all the original data.
1151            mDataAdapter.flush();
1152            mFilmstripController.setDataAdapter(mDataAdapter);
1153        }
1154
1155        setupNfcBeamPush();
1156
1157        mLocalImagesObserver = new LocalMediaObserver();
1158        mLocalVideosObserver = new LocalMediaObserver();
1159
1160        getContentResolver().registerContentObserver(
1161                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1162                mLocalImagesObserver);
1163        getContentResolver().registerContentObserver(
1164                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1165                mLocalVideosObserver);
1166        if (FeedbackHelper.feedbackAvailable()) {
1167            mFeedbackHelper = new FeedbackHelper(mAppContext);
1168        }
1169    }
1170
1171    private void setRotationAnimation() {
1172        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1173        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1174        Window win = getWindow();
1175        WindowManager.LayoutParams winParams = win.getAttributes();
1176        winParams.rotationAnimation = rotationAnimation;
1177        win.setAttributes(winParams);
1178    }
1179
1180    @Override
1181    public void onUserInteraction() {
1182        super.onUserInteraction();
1183        if (!isFinishing()) {
1184            keepScreenOnForAWhile();
1185        }
1186    }
1187
1188    @Override
1189    public boolean dispatchTouchEvent(MotionEvent ev) {
1190        boolean result = super.dispatchTouchEvent(ev);
1191        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1192            // Real deletion is postponed until the next user interaction after
1193            // the gesture that triggers deletion. Until real deletion is
1194            // performed, users can click the undo button to bring back the
1195            // image that they chose to delete.
1196            if (mPendingDeletion && !mIsUndoingDeletion) {
1197                performDeletion();
1198            }
1199        }
1200        return result;
1201    }
1202
1203    @Override
1204    public void onPause() {
1205        mPaused = true;
1206        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
1207
1208        // Delete photos that are pending deletion
1209        performDeletion();
1210        mCurrentModule.pause();
1211        mOrientationManager.pause();
1212        // Close the camera and wait for the operation done.
1213        mCameraController.closeCamera();
1214        mPanoramaViewHelper.onPause();
1215
1216        mLocalImagesObserver.setActivityPaused(true);
1217        mLocalVideosObserver.setActivityPaused(true);
1218        resetScreenOn();
1219        super.onPause();
1220    }
1221
1222    @Override
1223    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1224        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1225            mResetToPreviewOnResume = false;
1226        } else {
1227            super.onActivityResult(requestCode, resultCode, data);
1228        }
1229    }
1230
1231    @Override
1232    public void onResume() {
1233        mPaused = false;
1234        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
1235
1236        mLastLayoutOrientation = getResources().getConfiguration().orientation;
1237
1238        // TODO: Handle this in OrientationManager.
1239        // Auto-rotate off
1240        if (Settings.System.getInt(getContentResolver(),
1241                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1242            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1243            mAutoRotateScreen = false;
1244        } else {
1245            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1246            mAutoRotateScreen = true;
1247        }
1248
1249        if (isCaptureIntent()) {
1250            // Foreground event caused by photo or video capure intent.
1251            UsageStatistics.foregrounded(
1252                    eventprotos.ForegroundEvent.ForegroundSource.INTENT_PICKER);
1253        } else if (!mSecureCamera) {
1254            // Foreground event that is not caused by an intent.
1255            UsageStatistics.foregrounded(
1256                    eventprotos.ForegroundEvent.ForegroundSource.ICON_LAUNCHER);
1257        }
1258
1259        Drawable galleryLogo;
1260        if (mSecureCamera) {
1261            mGalleryIntent = null;
1262            galleryLogo = null;
1263        } else {
1264            mGalleryIntent = IntentHelper.getDefaultGalleryIntent(mAppContext);
1265            galleryLogo = IntentHelper.getGalleryIcon(mAppContext, mGalleryIntent);
1266        }
1267        if (galleryLogo == null) {
1268            try {
1269                galleryLogo = getPackageManager().getActivityLogo(getComponentName());
1270            } catch (PackageManager.NameNotFoundException e) {
1271                Log.e(TAG, "Can't get the activity logo");
1272            }
1273        }
1274        mActionBar.setLogo(galleryLogo);
1275        mOrientationManager.resume();
1276        super.onResume();
1277        mCurrentModule.resume();
1278        setSwipingEnabled(true);
1279
1280        if (mResetToPreviewOnResume) {
1281            mCameraAppUI.resume();
1282        } else {
1283            LocalData data = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1284            if (data != null) {
1285                mDataAdapter.refresh(data.getContentUri(), false);
1286            }
1287        }
1288        // The share button might be disabled to avoid double tapping.
1289        mCameraAppUI.getFilmstripBottomControls().setShareEnabled(true);
1290        // Default is showing the preview, unless disabled by explicitly
1291        // starting an activity we want to return from to the filmstrip rather
1292        // than the preview.
1293        mResetToPreviewOnResume = true;
1294
1295        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1296                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1297            if (!mSecureCamera) {
1298                // If it's secure camera, requestLoad() should not be called
1299                // as it will load all the data.
1300                if (!mFilmstripVisible) {
1301                    mDataAdapter.requestLoad();
1302                }
1303            }
1304        }
1305        mLocalImagesObserver.setActivityPaused(false);
1306        mLocalVideosObserver.setActivityPaused(false);
1307
1308        keepScreenOnForAWhile();
1309
1310        // Lights-out mode at all times.
1311        findViewById(R.id.activity_root_view)
1312                .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
1313        mPanoramaViewHelper.onResume();
1314        ReleaseDialogHelper.maybeShowDialog(this, mSettingsManager);
1315    }
1316
1317    @Override
1318    public void onStart() {
1319        super.onStart();
1320        mPanoramaViewHelper.onStart();
1321        boolean recordLocation = RecordLocationPreference.get(
1322                mPreferences, mContentResolver);
1323        mLocationManager.recordLocation(recordLocation);
1324    }
1325
1326    @Override
1327    protected void onStop() {
1328        mPanoramaViewHelper.onStop();
1329        if (mFeedbackHelper != null) {
1330            mFeedbackHelper.stopFeedback();
1331        }
1332
1333        mLocationManager.disconnect();
1334        CameraManagerFactory.recycle();
1335        super.onStop();
1336    }
1337
1338    @Override
1339    public void onDestroy() {
1340        if (mSecureCamera) {
1341            unregisterReceiver(mScreenOffReceiver);
1342        }
1343        mActionBar.removeOnMenuVisibilityListener(this);
1344        mSettingsManager.removeAllListeners();
1345        mCameraController.removeCallbackReceiver();
1346        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1347        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1348        getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
1349        mCameraAppUI.onDestroy();
1350        mCameraController = null;
1351        mSettingsManager = null;
1352        mCameraAppUI = null;
1353        mOrientationManager = null;
1354        mButtonManager = null;
1355        super.onDestroy();
1356    }
1357
1358    @Override
1359    public void onConfigurationChanged(Configuration config) {
1360        super.onConfigurationChanged(config);
1361        Log.v(TAG, "onConfigurationChanged");
1362        if (config.orientation == Configuration.ORIENTATION_UNDEFINED) {
1363            return;
1364        }
1365
1366        if (mLastLayoutOrientation != config.orientation) {
1367            mLastLayoutOrientation = config.orientation;
1368            mCurrentModule.onLayoutOrientationChanged(
1369                    mLastLayoutOrientation == Configuration.ORIENTATION_LANDSCAPE);
1370        }
1371    }
1372
1373    @Override
1374    public boolean onKeyDown(int keyCode, KeyEvent event) {
1375        if (!mFilmstripVisible) {
1376            if (mCurrentModule.onKeyDown(keyCode, event)) {
1377                return true;
1378            }
1379            // Prevent software keyboard or voice search from showing up.
1380            if (keyCode == KeyEvent.KEYCODE_SEARCH
1381                    || keyCode == KeyEvent.KEYCODE_MENU) {
1382                if (event.isLongPress()) {
1383                    return true;
1384                }
1385            }
1386        }
1387
1388        return super.onKeyDown(keyCode, event);
1389    }
1390
1391    @Override
1392    public boolean onKeyUp(int keyCode, KeyEvent event) {
1393        if (!mFilmstripVisible && mCurrentModule.onKeyUp(keyCode, event)) {
1394            return true;
1395        }
1396        return super.onKeyUp(keyCode, event);
1397    }
1398
1399    @Override
1400    public void onBackPressed() {
1401        if (!mCameraAppUI.onBackPressed()) {
1402            if (!mCurrentModule.onBackPressed()) {
1403                super.onBackPressed();
1404            }
1405        }
1406    }
1407
1408    public boolean isAutoRotateScreen() {
1409        return mAutoRotateScreen;
1410    }
1411
1412    protected void updateStorageSpace() {
1413        mStorageSpaceBytes = Storage.getAvailableSpace();
1414    }
1415
1416    protected long getStorageSpaceBytes() {
1417        return mStorageSpaceBytes;
1418    }
1419
1420    protected void updateStorageSpaceAndHint() {
1421        updateStorageSpace();
1422        updateStorageHint(mStorageSpaceBytes);
1423    }
1424
1425    protected void updateStorageHint(long storageSpace) {
1426        String message = null;
1427        if (storageSpace == Storage.UNAVAILABLE) {
1428            message = getString(R.string.no_storage);
1429        } else if (storageSpace == Storage.PREPARING) {
1430            message = getString(R.string.preparing_sd);
1431        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1432            message = getString(R.string.access_sd_fail);
1433        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1434            message = getString(R.string.spaceIsLow_content);
1435        }
1436
1437        if (message != null) {
1438            if (mStorageHint == null) {
1439                mStorageHint = OnScreenHint.makeText(mAppContext, message);
1440            } else {
1441                mStorageHint.setText(message);
1442            }
1443            mStorageHint.show();
1444        } else if (mStorageHint != null) {
1445            mStorageHint.cancel();
1446            mStorageHint = null;
1447        }
1448    }
1449
1450    protected void setResultEx(int resultCode) {
1451        mResultCodeForTesting = resultCode;
1452        setResult(resultCode);
1453    }
1454
1455    protected void setResultEx(int resultCode, Intent data) {
1456        mResultCodeForTesting = resultCode;
1457        mResultDataForTesting = data;
1458        setResult(resultCode, data);
1459    }
1460
1461    public int getResultCode() {
1462        return mResultCodeForTesting;
1463    }
1464
1465    public Intent getResultData() {
1466        return mResultDataForTesting;
1467    }
1468
1469    public boolean isSecureCamera() {
1470        return mSecureCamera;
1471    }
1472
1473    @Override
1474    public boolean isPaused() {
1475        return mPaused;
1476    }
1477
1478    @Override
1479    public void onModeSelected(int modeIndex) {
1480        if (mCurrentModeIndex == modeIndex) {
1481            return;
1482        }
1483
1484        CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
1485        // Record last used camera mode for quick switching
1486        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
1487                || modeIndex == getResources().getInteger(R.integer.camera_mode_gcam)) {
1488            mSettingsManager.setInt(SettingsManager.SETTING_KEY_CAMERA_MODULE_LAST_USED_INDEX,
1489                    modeIndex);
1490        }
1491
1492        closeModule(mCurrentModule);
1493        int oldModuleIndex = mCurrentModeIndex;
1494
1495        // Refocus and Gcam are modes that cannot be selected
1496        // from the mode list view, because they are not list items.
1497        // Check whether we should interpret MODULE_CRAFT as either.
1498        if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)) {
1499            boolean hdrPlusOn = mSettingsManager.isHdrPlusOn();
1500            if (hdrPlusOn && GcamHelper.hasGcamCapture()) {
1501                modeIndex = getResources().getInteger(R.integer.camera_mode_gcam);
1502            }
1503        }
1504
1505        setModuleFromModeIndex(modeIndex);
1506        mCameraAppUI.clearModuleUI();
1507
1508        mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
1509        openModule(mCurrentModule);
1510        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1511        // Store the module index so we can use it the next time the Camera
1512        // starts up.
1513        SharedPreferences prefs = PreferenceManager
1514                .getDefaultSharedPreferences(mAppContext);
1515        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1516    }
1517
1518    /**
1519     * Shows the settings dialog.
1520     */
1521    @Override
1522    public void onSettingsSelected() {
1523        Intent intent = new Intent(this, CameraSettingsActivity.class);
1524        startActivity(intent);
1525    }
1526
1527    /**
1528     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1529     * index an sets it as mCurrentModule.
1530     */
1531    private void setModuleFromModeIndex(int modeIndex) {
1532        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1533        if (agent == null) {
1534            return;
1535        }
1536        if (!agent.requestAppForCamera()) {
1537            mCameraController.closeCamera();
1538        }
1539        mCurrentModeIndex = agent.getModuleId();
1540        mCurrentModule = (CameraModule) agent.createModule(this);
1541    }
1542
1543    @Override
1544    public SettingsManager getSettingsManager() {
1545        return mSettingsManager;
1546    }
1547
1548    @Override
1549    public CameraServices getServices() {
1550        return (CameraServices) getApplication();
1551    }
1552
1553    public List<String> getSupportedModeNames() {
1554        List<Integer> indices = mModuleManager.getSupportedModeIndexList();
1555        List<String> supported = new ArrayList<String>();
1556
1557        for (Integer modeIndex : indices) {
1558            String name = CameraUtil.getCameraModeText(modeIndex, mAppContext);
1559            if (name != null && !name.equals("")) {
1560                supported.add(name);
1561            }
1562        }
1563        return supported;
1564    }
1565
1566    @Override
1567    public ButtonManager getButtonManager() {
1568        if (mButtonManager == null) {
1569            mButtonManager = new ButtonManager(this);
1570        }
1571        return mButtonManager;
1572    }
1573
1574    /**
1575     * Creates an AlertDialog appropriate for choosing whether to enable
1576     * location on the first run of the app.
1577     */
1578    public AlertDialog getFirstTimeLocationAlert() {
1579        AlertDialog.Builder builder = new AlertDialog.Builder(this);
1580        builder = SettingsUtil.getFirstTimeLocationAlertBuilder(builder, new Callback<Boolean>() {
1581            @Override
1582            public void onCallback(Boolean locationOn) {
1583                mSettingsManager.setLocation(locationOn, mLocationManager);
1584            }
1585        });
1586        if (builder != null) {
1587            return builder.create();
1588        } else {
1589            return null;
1590        }
1591    }
1592
1593    /**
1594     * Launches an ACTION_EDIT intent for the given local data item. If
1595     * 'withTinyPlanet' is set, this will show a disambig dialog first to let
1596     * the user start either the tiny planet editor or another photo edior.
1597     *
1598     * @param data The data item to edit.
1599     */
1600    public void launchEditor(LocalData data) {
1601        Intent intent = new Intent(Intent.ACTION_EDIT)
1602                .setDataAndType(data.getContentUri(), data.getMimeType())
1603                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1604        try {
1605            launchActivityByIntent(intent);
1606        } catch (ActivityNotFoundException e) {
1607            launchActivityByIntent(Intent.createChooser(intent, null));
1608        }
1609    }
1610
1611    @Override
1612    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1613        super.onCreateContextMenu(menu, v, menuInfo);
1614
1615        MenuInflater inflater = getMenuInflater();
1616        inflater.inflate(R.menu.filmstrip_context_menu, menu);
1617    }
1618
1619    @Override
1620    public boolean onContextItemSelected(MenuItem item) {
1621        switch (item.getItemId()) {
1622            case R.id.tiny_planet_editor:
1623                mMyFilmstripBottomControlListener.onTinyPlanet();
1624                return true;
1625            case R.id.photo_editor:
1626                mMyFilmstripBottomControlListener.onEdit();
1627                return true;
1628        }
1629        return false;
1630    }
1631
1632    /**
1633     * Launch the tiny planet editor.
1634     *
1635     * @param data The data must be a 360 degree stereographically mapped
1636     *            panoramic image. It will not be modified, instead a new item
1637     *            with the result will be added to the filmstrip.
1638     */
1639    public void launchTinyPlanetEditor(LocalData data) {
1640        TinyPlanetFragment fragment = new TinyPlanetFragment();
1641        Bundle bundle = new Bundle();
1642        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1643        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1644        fragment.setArguments(bundle);
1645        fragment.show(getFragmentManager(), "tiny_planet");
1646    }
1647
1648    private void openModule(CameraModule module) {
1649        module.init(this, isSecureCamera(), isCaptureIntent());
1650        module.resume();
1651        module.onPreviewVisibilityChanged(!mFilmstripVisible);
1652    }
1653
1654    private void closeModule(CameraModule module) {
1655        module.pause();
1656    }
1657
1658    private void performDeletion() {
1659        if (!mPendingDeletion) {
1660            return;
1661        }
1662        hideUndoDeletionBar(false);
1663        mDataAdapter.executeDeletion();
1664    }
1665
1666    public void showUndoDeletionBar() {
1667        if (mPendingDeletion) {
1668            performDeletion();
1669        }
1670        Log.v(TAG, "showing undo bar");
1671        mPendingDeletion = true;
1672        if (mUndoDeletionBar == null) {
1673            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1674                    mAboveFilmstripControlLayout, true);
1675            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1676            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1677            button.setOnClickListener(new View.OnClickListener() {
1678                @Override
1679                public void onClick(View view) {
1680                    mDataAdapter.undoDataRemoval();
1681                    hideUndoDeletionBar(true);
1682                }
1683            });
1684            // Setting undo bar clickable to avoid touch events going through
1685            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1686            mUndoDeletionBar.setClickable(true);
1687            // When there is user interaction going on with the undo button, we
1688            // do not want to hide the undo bar.
1689            button.setOnTouchListener(new View.OnTouchListener() {
1690                @Override
1691                public boolean onTouch(View v, MotionEvent event) {
1692                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1693                        mIsUndoingDeletion = true;
1694                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1695                        mIsUndoingDeletion = false;
1696                    }
1697                    return false;
1698                }
1699            });
1700        }
1701        mUndoDeletionBar.setAlpha(0f);
1702        mUndoDeletionBar.setVisibility(View.VISIBLE);
1703        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1704    }
1705
1706    private void hideUndoDeletionBar(boolean withAnimation) {
1707        Log.v(TAG, "Hiding undo deletion bar");
1708        mPendingDeletion = false;
1709        if (mUndoDeletionBar != null) {
1710            if (withAnimation) {
1711                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1712                        .setListener(new Animator.AnimatorListener() {
1713                            @Override
1714                            public void onAnimationStart(Animator animation) {
1715                                // Do nothing.
1716                            }
1717
1718                            @Override
1719                            public void onAnimationEnd(Animator animation) {
1720                                mUndoDeletionBar.setVisibility(View.GONE);
1721                            }
1722
1723                            @Override
1724                            public void onAnimationCancel(Animator animation) {
1725                                // Do nothing.
1726                            }
1727
1728                            @Override
1729                            public void onAnimationRepeat(Animator animation) {
1730                                // Do nothing.
1731                            }
1732                        }).start();
1733            } else {
1734                mUndoDeletionBar.setVisibility(View.GONE);
1735            }
1736        }
1737    }
1738
1739    @Override
1740    public void onOrientationChanged(int orientation) {
1741        // We keep the last known orientation. So if the user first orient
1742        // the camera then point the camera to floor or sky, we still have
1743        // the correct orientation.
1744        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1745            return;
1746        }
1747        mLastRawOrientation = orientation;
1748        if (mCurrentModule != null) {
1749            mCurrentModule.onOrientationChanged(orientation);
1750        }
1751    }
1752
1753    /**
1754     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1755     * capture intent.
1756     *
1757     * @param enable {@code true} to enable swipe.
1758     */
1759    public void setSwipingEnabled(boolean enable) {
1760        // TODO: Bring back the functionality.
1761        if (isCaptureIntent()) {
1762            // lockPreview(true);
1763        } else {
1764            // lockPreview(!enable);
1765        }
1766    }
1767
1768    // Accessor methods for getting latency times used in performance testing
1769    public long getFirstPreviewTime() {
1770        if (mCurrentModule instanceof PhotoModule) {
1771            long coverHiddenTime = getCameraAppUI().getCoverHiddenTime();
1772            if (coverHiddenTime != -1) {
1773                return coverHiddenTime - mOnCreateTime;
1774            }
1775        }
1776        return -1;
1777    }
1778
1779    public long getAutoFocusTime() {
1780        return (mCurrentModule instanceof PhotoModule) ?
1781                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1782    }
1783
1784    public long getShutterLag() {
1785        return (mCurrentModule instanceof PhotoModule) ?
1786                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1787    }
1788
1789    public long getShutterToPictureDisplayedTime() {
1790        return (mCurrentModule instanceof PhotoModule) ?
1791                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1792    }
1793
1794    public long getPictureDisplayedToJpegCallbackTime() {
1795        return (mCurrentModule instanceof PhotoModule) ?
1796                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1797    }
1798
1799    public long getJpegCallbackFinishTime() {
1800        return (mCurrentModule instanceof PhotoModule) ?
1801                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1802    }
1803
1804    public long getCaptureStartTime() {
1805        return (mCurrentModule instanceof PhotoModule) ?
1806                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1807    }
1808
1809    public boolean isRecording() {
1810        return (mCurrentModule instanceof VideoModule) ?
1811                ((VideoModule) mCurrentModule).isRecording() : false;
1812    }
1813
1814    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1815        return mCameraController;
1816    }
1817
1818    // For debugging purposes only.
1819    public CameraModule getCurrentModule() {
1820        return mCurrentModule;
1821    }
1822
1823    /**
1824     * Reads the current location recording settings and passes it on to the
1825     * location manager.
1826     */
1827    public void syncLocationManagerSetting() {
1828        mSettingsManager.syncLocationManager(mLocationManager);
1829    }
1830
1831    private void keepScreenOnForAWhile() {
1832        if (mKeepScreenOn) {
1833            return;
1834        }
1835        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1836        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1837        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1838    }
1839
1840    private void resetScreenOn() {
1841        mKeepScreenOn = false;
1842        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1843        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1844    }
1845
1846    /**
1847     * @return {@code true} if the Gallery is launched successfully.
1848     */
1849    private boolean startGallery() {
1850        if (mGalleryIntent == null) {
1851            return false;
1852        }
1853        try {
1854            UsageStatistics.changeScreen(NavigationChange.Mode.GALLERY, InteractionCause.BUTTON);
1855            Intent startGalleryIntent = new Intent(mGalleryIntent);
1856            int currentDataId = mFilmstripController.getCurrentId();
1857            LocalData currentLocalData = mDataAdapter.getLocalData(currentDataId);
1858            if (currentLocalData != null) {
1859                GalleryHelper.setContentUri(startGalleryIntent, currentLocalData.getContentUri());
1860            }
1861            launchActivityByIntent(startGalleryIntent);
1862        } catch (ActivityNotFoundException e) {
1863            Log.w(TAG, "Failed to launch gallery activity, closing");
1864        }
1865        return false;
1866    }
1867
1868    private void setNfcBeamPushUriFromData(LocalData data) {
1869        final Uri uri = data.getContentUri();
1870        if (uri != Uri.EMPTY) {
1871            mNfcPushUris[0] = uri;
1872        } else {
1873            mNfcPushUris[0] = null;
1874        }
1875    }
1876
1877    /**
1878     * Updates the visibility of the filmstrip bottom controls.
1879     */
1880    private void updateUiByData(final int dataId) {
1881        if (isSecureCamera()) {
1882            // We cannot show buttons in secure camera since go to other
1883            // activities might create a security hole.
1884            return;
1885        }
1886
1887        final LocalData currentData = mDataAdapter.getLocalData(dataId);
1888        if (currentData == null) {
1889            Log.w(TAG, "Current data ID not found.");
1890            hideSessionProgress();
1891            return;
1892        }
1893
1894        setNfcBeamPushUriFromData(currentData);
1895
1896        /* Bottom controls. */
1897
1898        updateBottomControlsByData(currentData);
1899        if (!mDataAdapter.isMetadataUpdated(dataId)) {
1900            mDataAdapter.updateMetadata(dataId);
1901        }
1902    }
1903
1904    /**
1905     * Updates the bottom controls based on the data.
1906     */
1907    private void updateBottomControlsByData(final LocalData currentData) {
1908
1909        final CameraAppUI.BottomControls filmstripBottomControls =
1910                mCameraAppUI.getFilmstripBottomControls();
1911        filmstripBottomControls.setEditButtonVisibility(
1912                currentData.isDataActionSupported(LocalData.DATA_ACTION_EDIT));
1913        filmstripBottomControls.setShareButtonVisibility(
1914                currentData.isDataActionSupported(LocalData.DATA_ACTION_SHARE));
1915        filmstripBottomControls.setDeleteButtonVisibility(
1916                currentData.isDataActionSupported(LocalData.DATA_ACTION_DELETE));
1917
1918        /* Progress bar */
1919
1920        Uri contentUri = currentData.getContentUri();
1921        CaptureSessionManager sessionManager = getServices()
1922                .getCaptureSessionManager();
1923        int sessionProgress = sessionManager.getSessionProgress(contentUri);
1924
1925        if (sessionProgress < 0) {
1926            hideSessionProgress();
1927        } else {
1928            CharSequence progressMessage = sessionManager
1929                    .getSessionProgressMessage(contentUri);
1930            showSessionProgress(progressMessage);
1931            updateSessionProgress(sessionProgress);
1932        }
1933
1934        /* View button */
1935
1936        // We need to add this to a separate DB.
1937        final int viewButtonVisibility;
1938        if (PanoramaMetadataLoader.isPanorama(currentData)) {
1939            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_PHOTO_SPHERE;
1940        } else if (RgbzMetadataLoader.hasRGBZData(currentData)) {
1941            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_REFOCUS;
1942        } else {
1943            viewButtonVisibility = CameraAppUI.BottomControls.VIEWER_NONE;
1944        }
1945
1946        filmstripBottomControls.setTinyPlanetEnabled(
1947                PanoramaMetadataLoader.isPanorama360(currentData));
1948        filmstripBottomControls.setViewerButtonVisibility(viewButtonVisibility);
1949    }
1950}
1951