CameraActivity.java revision 635a4b8ba2b8684f95e5cb0aa35c244a06985f4c
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.content.ActivityNotFoundException;
24import android.content.BroadcastReceiver;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.SharedPreferences;
30import android.content.pm.ActivityInfo;
31import android.content.res.Configuration;
32import android.graphics.Bitmap;
33import android.graphics.Color;
34import android.graphics.SurfaceTexture;
35import android.graphics.drawable.ColorDrawable;
36import android.net.Uri;
37import android.nfc.NfcAdapter;
38import android.nfc.NfcAdapter.CreateBeamUrisCallback;
39import android.nfc.NfcEvent;
40import android.os.AsyncTask;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Looper;
45import android.os.Message;
46import android.preference.PreferenceManager;
47import android.provider.MediaStore;
48import android.provider.Settings;
49import android.util.Log;
50import android.view.Gravity;
51import android.view.KeyEvent;
52import android.view.LayoutInflater;
53import android.view.Menu;
54import android.view.MenuInflater;
55import android.view.MenuItem;
56import android.view.MotionEvent;
57import android.view.View;
58import android.view.ViewGroup;
59import android.view.Window;
60import android.view.WindowManager;
61import android.widget.FrameLayout;
62import android.widget.FrameLayout.LayoutParams;
63import android.widget.ImageView;
64import android.widget.PopupWindow;
65import android.widget.ProgressBar;
66import android.widget.ShareActionProvider;
67
68import com.android.camera.app.AppController;
69import com.android.camera.app.AppManagerFactory;
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.ImageTaskManager;
77import com.android.camera.app.MediaSaver;
78import com.android.camera.app.ModuleManagerImpl;
79import com.android.camera.app.OrientationManager;
80import com.android.camera.app.OrientationManagerImpl;
81import com.android.camera.app.PanoramaStitchingManager;
82import com.android.camera.app.PlaceholderManager;
83import com.android.camera.crop.CropActivity;
84import com.android.camera.data.CameraDataAdapter;
85import com.android.camera.data.CameraPreviewData;
86import com.android.camera.data.FixedFirstDataAdapter;
87import com.android.camera.data.FixedLastDataAdapter;
88import com.android.camera.data.InProgressDataWrapper;
89import com.android.camera.data.LocalData;
90import com.android.camera.data.LocalDataAdapter;
91import com.android.camera.data.LocalMediaObserver;
92import com.android.camera.data.MediaDetails;
93import com.android.camera.data.SimpleViewData;
94import com.android.camera.filmstrip.FilmstripController;
95import com.android.camera.filmstrip.FilmstripImageData;
96import com.android.camera.filmstrip.FilmstripListener;
97import com.android.camera.module.ModulesInfo;
98import com.android.camera.settings.SettingsManager;
99import com.android.camera.settings.SettingsManager.SettingsCapabilities;
100import com.android.camera.tinyplanet.TinyPlanetFragment;
101import com.android.camera.ui.CameraControls;
102import com.android.camera.ui.DetailsDialog;
103import com.android.camera.ui.FilmstripView;
104import com.android.camera.ui.MainActivityLayout;
105import com.android.camera.ui.ModeListView;
106import com.android.camera.ui.SettingsView;
107import com.android.camera.util.ApiHelper;
108import com.android.camera.util.CameraUtil;
109import com.android.camera.util.GcamHelper;
110import com.android.camera.util.IntentHelper;
111import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
112import com.android.camera.util.UsageStatistics;
113import com.android.camera2.R;
114
115import java.io.File;
116import java.util.List;
117
118public class CameraActivity extends Activity
119        implements AppController, CameraManager.CameraOpenCallback,
120        ActionBar.OnMenuVisibilityListener, ShareActionProvider.OnShareTargetSelectedListener,
121        OrientationManager.OnOrientationChangeListener {
122
123    private static final String TAG = "CAM_Activity";
124
125    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
126            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
127    public static final String ACTION_IMAGE_CAPTURE_SECURE =
128            "android.media.action.IMAGE_CAPTURE_SECURE";
129    public static final String ACTION_TRIM_VIDEO =
130            "com.android.camera.action.TRIM";
131    public static final String MEDIA_ITEM_PATH = "media-item-path";
132
133    // The intent extra for camera from secure lock screen. True if the gallery
134    // should only show newly captured pictures. sSecureAlbumId does not
135    // increment. This is used when switching between camera, camcorder, and
136    // panorama. If the extra is not set, it is in the normal camera mode.
137    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
138
139    /**
140     * Request code from an activity we started that indicated that we do not want
141     * to reset the view to the preview in onResume.
142     */
143    public static final int REQ_CODE_DONT_SWITCH_TO_PREVIEW = 142;
144
145    public static final int REQ_CODE_GCAM_DEBUG_POSTCAPTURE = 999;
146
147    private static final int MSG_HIDE_ACTION_BAR = 1;
148    private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
149    private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
150    private static final long SCREEN_DELAY_MS = 2 * 60 * 1000;  // 2 mins.
151
152    /**
153     * Whether onResume should reset the view to the preview.
154     */
155    private boolean mResetToPreviewOnResume = true;
156
157    // Supported operations at FilmStripView. Different data has different
158    // set of supported operations.
159    private static final int SUPPORT_DELETE = 1 << 0;
160    private static final int SUPPORT_ROTATE = 1 << 1;
161    private static final int SUPPORT_INFO = 1 << 2;
162    private static final int SUPPORT_CROP = 1 << 3;
163    private static final int SUPPORT_SETAS = 1 << 4;
164    private static final int SUPPORT_EDIT = 1 << 5;
165    private static final int SUPPORT_TRIM = 1 << 6;
166    private static final int SUPPORT_SHARE = 1 << 7;
167    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
168    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
169    private static final int SUPPORT_ALL = 0xffffffff;
170
171    /**
172     * This data adapter is used by FilmStripView.
173     */
174    private LocalDataAdapter mDataAdapter;
175    /**
176     * This data adapter represents the real local camera data.
177     */
178    private LocalDataAdapter mWrappedDataAdapter;
179
180    private SettingsManager mSettingsManager;
181    private SettingsController mSettingsController;
182    private PanoramaStitchingManager mPanoramaManager;
183    private PlaceholderManager mPlaceholderManager;
184    private ModeListView mModeListView;
185    private int mCurrentModeIndex;
186    private CameraModule mCurrentModule;
187    private ModuleManagerImpl mModuleManager;
188    private FrameLayout mAboveFilmstripControlLayout;
189    private FrameLayout mCameraModuleRootView;
190    private FilmstripController mFilmstripController;
191    private ProgressBar mBottomProgress;
192    private View mPanoStitchingPanel;
193    private int mResultCodeForTesting;
194    private Intent mResultDataForTesting;
195    private OnScreenHint mStorageHint;
196    private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
197    private boolean mAutoRotateScreen;
198    private boolean mSecureCamera;
199    private int mLastRawOrientation;
200    private OrientationManagerImpl mOrientationManager;
201    private LocationManager mLocationManager;
202    private Handler mMainHandler;
203    private PanoramaViewHelper mPanoramaViewHelper;
204    private CameraPreviewData mCameraPreviewData;
205    private ActionBar mActionBar;
206    private OnActionBarVisibilityListener mOnActionBarVisibilityListener = null;
207    private Menu mActionBarMenu;
208    private ViewGroup mUndoDeletionBar;
209    private boolean mIsUndoingDeletion = false;
210
211    private final Uri[] mNfcPushUris = new Uri[1];
212
213    private ShareActionProvider mStandardShareActionProvider;
214    private Intent mStandardShareIntent;
215    private ShareActionProvider mPanoramaShareActionProvider;
216    private Intent mPanoramaShareIntent;
217    private LocalMediaObserver mLocalImagesObserver;
218    private LocalMediaObserver mLocalVideosObserver;
219
220    private final int DEFAULT_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
221            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
222    private boolean mPendingDeletion = false;
223
224    private Intent mVideoShareIntent;
225    private Intent mImageShareIntent;
226
227    private CameraController mCameraController;
228    private boolean mPaused;
229    private CameraAppUI mCameraAppUI;
230
231    private MediaSaver mMediaSaver;
232
233    // close activity when screen turns off
234    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
235        @Override
236        public void onReceive(Context context, Intent intent) {
237            finish();
238        }
239    };
240
241    private static BroadcastReceiver sScreenOffReceiver;
242
243    /**
244     * Whether the screen is kept turned on.
245     */
246    private boolean mKeepScreenOn;
247
248    @Override
249    public void onCameraOpened(CameraManager.CameraProxy camera) {
250        if (!mModuleManager.getModuleAgent(mCurrentModeIndex).requestAppForCamera()) {
251            // We shouldn't be here. Just close the camera and leave.
252            camera.release(false);
253            throw new IllegalStateException("Camera opened but the module shouldn't be " +
254                    "requesting");
255        }
256        if (mCurrentModule != null) {
257            SettingsCapabilities capabilities =
258                SettingsController.getSettingsCapabilities(camera);
259            mSettingsManager.changeCamera(camera.getCameraId(), capabilities);
260            mCurrentModule.onCameraAvailable(camera);
261        }
262    }
263
264    @Override
265    public void onCameraDisabled(int cameraId) {
266        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_OPEN_FAIL,
267                "security");
268
269        CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
270    }
271
272    @Override
273    public void onDeviceOpenFailure(int cameraId) {
274        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
275                UsageStatistics.ACTION_OPEN_FAIL, "open");
276
277        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
278    }
279
280    @Override
281    public void onReconnectionFailure(CameraManager mgr) {
282        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
283                UsageStatistics.ACTION_OPEN_FAIL, "reconnect");
284
285        CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
286    }
287
288    private class MainHandler extends Handler {
289        public MainHandler(Looper looper) {
290            super(looper);
291        }
292
293        @Override
294        public void handleMessage(Message msg) {
295            switch (msg.what) {
296                case MSG_HIDE_ACTION_BAR: {
297                    removeMessages(MSG_HIDE_ACTION_BAR);
298                    CameraActivity.this.setSystemBarsVisibility(false);
299                    break;
300                }
301
302                case MSG_CLEAR_SCREEN_ON_FLAG:  {
303                    if (!mPaused) {
304                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
305                    }
306                    break;
307                }
308
309                default:
310            }
311        }
312    }
313
314    public interface OnActionBarVisibilityListener {
315        public void onActionBarVisibilityChanged(boolean isVisible);
316    }
317
318    public void setOnActionBarVisibilityListener(OnActionBarVisibilityListener listener) {
319        mOnActionBarVisibilityListener = listener;
320    }
321
322    private String fileNameFromDataID(int dataID) {
323        final LocalData localData = mDataAdapter.getLocalData(dataID);
324
325        File localFile = new File(localData.getPath());
326        return localFile.getName();
327    }
328
329    private final FilmstripListener mFilmStripListener =
330            new FilmstripListener() {
331                @Override
332                public void onDataPromoted(int dataID) {
333                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
334                            UsageStatistics.ACTION_DELETE, "promoted", 0,
335                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
336
337                    removeData(dataID);
338                }
339
340                @Override
341                public void onDataDemoted(int dataID) {
342                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
343                            UsageStatistics.ACTION_DELETE, "demoted", 0,
344                            UsageStatistics.hashFileName(fileNameFromDataID(dataID)));
345
346                    removeData(dataID);
347                }
348
349                @Override
350                public void onDataFullScreenChange(int dataID, boolean full) {
351                    boolean isCameraID = isCameraPreview(dataID);
352                    if (!isCameraID) {
353                        if (!full) {
354                            // Always show action bar in filmstrip mode
355                            CameraActivity.this.setSystemBarsVisibility(true, false);
356                        } else if (mActionBar.isShowing()) {
357                            // Hide action bar after time out in full screen mode
358                            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR,
359                                    SHOW_ACTION_BAR_TIMEOUT_MS);
360                        }
361                    }
362                }
363
364                /**
365                 * Check if the local data corresponding to dataID is the camera
366                 * preview.
367                 *
368                 * @param dataID the ID of the local data
369                 * @return true if the local data is not null and it is the
370                 *         camera preview.
371                 */
372                private boolean isCameraPreview(int dataID) {
373                    LocalData localData = mDataAdapter.getLocalData(dataID);
374                    if (localData == null) {
375                        Log.w(TAG, "Current data ID not found.");
376                        return false;
377                    }
378                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
379                }
380
381                @Override
382                public void onDataReloaded() {
383                    setPreviewControlsVisibility(true);
384                    CameraActivity.this.setSystemBarsVisibility(false);
385                }
386
387                @Override
388                public void onCurrentDataCentered(int dataID) {
389                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
390                        // For now, We ignore all items that are not the camera preview.
391                        return;
392                    }
393
394                    if (!arePreviewControlsVisible()) {
395                        setPreviewControlsVisibility(true);
396                        CameraActivity.this.setSystemBarsVisibility(false);
397                    }
398                }
399
400                @Override
401                public void onCurrentDataOffCentered(int dataID) {
402                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
403                        // For now, We ignore all items that are not the camera preview.
404                        return;
405                    }
406
407                    if (arePreviewControlsVisible()) {
408                        setPreviewControlsVisibility(false);
409                    }
410                }
411
412                @Override
413                public void onDataFocusChanged(final int dataID, final boolean focused) {
414                    // Delay hiding action bar if there is any user interaction
415                    if (mMainHandler.hasMessages(MSG_HIDE_ACTION_BAR)) {
416                        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
417                        mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR,
418                                SHOW_ACTION_BAR_TIMEOUT_MS);
419                    }
420                    // TODO: This callback is UI event callback, should always
421                    // happen on UI thread. Find the reason for this
422                    // runOnUiThread() and fix it.
423                    runOnUiThread(new Runnable() {
424                        @Override
425                        public void run() {
426                            LocalData currentData = mDataAdapter.getLocalData(dataID);
427                            if (currentData == null) {
428                                Log.w(TAG, "Current data ID not found.");
429                                hidePanoStitchingProgress();
430                                return;
431                            }
432                            boolean isCameraID = currentData.getLocalDataType() ==
433                                    LocalData.LOCAL_CAMERA_PREVIEW;
434                            if (!focused) {
435                                if (isCameraID) {
436                                    mCurrentModule.onPreviewFocusChanged(false);
437                                    CameraActivity.this.setSystemBarsVisibility(true);
438                                }
439                                hidePanoStitchingProgress();
440                            } else {
441                                if (isCameraID) {
442                                    // Don't show the action bar in Camera
443                                    // preview.
444                                    CameraActivity.this.setSystemBarsVisibility(false);
445
446                                    if (mPendingDeletion) {
447                                        performDeletion();
448                                    }
449                                } else {
450                                    updateActionBarMenu(dataID);
451                                }
452
453                                Uri contentUri = currentData.getContentUri();
454                                if (contentUri == null) {
455                                    hidePanoStitchingProgress();
456                                    return;
457                                }
458                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
459                                if (panoStitchingProgress < 0) {
460                                    hidePanoStitchingProgress();
461                                    return;
462                                }
463                                showPanoStitchingProgress();
464                                updateStitchingProgress(panoStitchingProgress);
465                            }
466                        }
467                    });
468                }
469
470                @Override
471                public void onToggleSystemDecorsVisibility(int dataID) {
472                    // If action bar is showing, hide it immediately, otherwise
473                    // show action bar and hide it later
474                    if (mActionBar.isShowing()) {
475                        CameraActivity.this.setSystemBarsVisibility(false);
476                    } else {
477                        // Don't show the action bar if that is the camera preview.
478                        boolean isCameraID = isCameraPreview(dataID);
479                        if (!isCameraID) {
480                            CameraActivity.this.setSystemBarsVisibility(true, true);
481                        }
482                    }
483                }
484
485                @Override
486                public void setSystemDecorsVisibility(boolean visible) {
487                    CameraActivity.this.setSystemBarsVisibility(visible);
488                }
489            };
490
491    public void gotoGallery() {
492        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP,
493                "thumbnailTap");
494
495        mFilmstripController.goToNextItem();
496    }
497
498    /**
499     * If {@param visible} is false, this hides the action bar and switches the system UI
500     * to lights-out mode.
501     */
502    // TODO: This should not be called outside of the activity.
503    public void setSystemBarsVisibility(boolean visible) {
504        setSystemBarsVisibility(visible, false);
505    }
506
507    /**
508     * If {@param visible} is false, this hides the action bar and switches the
509     * system UI to lights-out mode. If {@param hideLater} is true, a delayed message
510     * will be sent after a timeout to hide the action bar.
511     */
512    private void setSystemBarsVisibility(boolean visible, boolean hideLater) {
513        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
514
515        int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
516        int newSystemUIVisibility = DEFAULT_SYSTEM_UI_VISIBILITY |
517                (visible ? View.SYSTEM_UI_FLAG_VISIBLE :
518                        View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
519        if (newSystemUIVisibility != currentSystemUIVisibility) {
520            mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility);
521        }
522
523        boolean currentActionBarVisibility = mActionBar.isShowing();
524        if (visible != currentActionBarVisibility) {
525            if (visible) {
526                mActionBar.show();
527            } else {
528                mActionBar.hide();
529            }
530            if (mOnActionBarVisibilityListener != null) {
531                mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible);
532            }
533        }
534
535        // Now delay hiding the bars
536        if (visible && hideLater) {
537            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
538        }
539    }
540
541    private void hidePanoStitchingProgress() {
542        mPanoStitchingPanel.setVisibility(View.GONE);
543    }
544
545    private void showPanoStitchingProgress() {
546        mPanoStitchingPanel.setVisibility(View.VISIBLE);
547    }
548
549    private void updateStitchingProgress(int progress) {
550        mBottomProgress.setProgress(progress);
551    }
552
553    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
554    private void setupNfcBeamPush() {
555        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(CameraActivity.this);
556        if (adapter == null) {
557            return;
558        }
559
560        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
561            // Disable beaming
562            adapter.setNdefPushMessage(null, CameraActivity.this);
563            return;
564        }
565
566        adapter.setBeamPushUris(null, CameraActivity.this);
567        adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
568            @Override
569            public Uri[] createBeamUris(NfcEvent event) {
570                return mNfcPushUris;
571            }
572        }, CameraActivity.this);
573    }
574
575    private void setNfcBeamPushUri(Uri uri) {
576        mNfcPushUris[0] = uri;
577    }
578
579    private void setStandardShareIntent(Uri contentUri, String mimeType) {
580        mStandardShareIntent = getShareIntentFromType(mimeType);
581        if (mStandardShareIntent != null) {
582            mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
583            mStandardShareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
584            if (mStandardShareActionProvider != null) {
585                mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
586            }
587        }
588    }
589
590    /**
591     * Get the share intent according to the mimeType
592     *
593     * @param mimeType The mimeType of current data.
594     * @return the video/image's ShareIntent or null if mimeType is invalid.
595     */
596    private Intent getShareIntentFromType(String mimeType) {
597        // Lazily create the intent object.
598        if (mimeType.startsWith("video/")) {
599            if (mVideoShareIntent == null) {
600                mVideoShareIntent = new Intent(Intent.ACTION_SEND);
601                mVideoShareIntent.setType("video/*");
602            }
603            return mVideoShareIntent;
604        } else if (mimeType.startsWith("image/")) {
605            if (mImageShareIntent == null) {
606                mImageShareIntent = new Intent(Intent.ACTION_SEND);
607                mImageShareIntent.setType("image/*");
608            }
609            return mImageShareIntent;
610        }
611        Log.w(TAG, "unsupported mimeType " + mimeType);
612        return null;
613    }
614
615    private void setPanoramaShareIntent(Uri contentUri) {
616        if (mPanoramaShareIntent == null) {
617            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
618        }
619        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
620        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
621        if (mPanoramaShareActionProvider != null) {
622            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
623        }
624    }
625
626    @Override
627    public void onMenuVisibilityChanged(boolean isVisible) {
628        // If menu is showing, we need to make sure action bar does not go away.
629        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
630        if (!isVisible) {
631            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
632        }
633    }
634
635    @Override
636    public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
637        int currentDataId = mFilmstripController.getCurrentId();
638        if (currentDataId < 0) {
639            return false;
640        }
641        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE,
642                intent.getComponent().getPackageName(), 0,
643                UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
644        return true;
645    }
646
647    /**
648     * According to the data type, make the menu items for supported operations
649     * visible.
650     *
651     * @param dataID the data ID of the current item.
652     */
653    private void updateActionBarMenu(int dataID) {
654        LocalData currentData = mDataAdapter.getLocalData(dataID);
655        if (currentData == null) {
656            return;
657        }
658        int type = currentData.getLocalDataType();
659
660        if (mActionBarMenu == null) {
661            return;
662        }
663
664        int supported = 0;
665
666        switch (type) {
667            case LocalData.LOCAL_IMAGE:
668                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
669                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
670                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
671                break;
672            case LocalData.LOCAL_VIDEO:
673                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
674                        | SUPPORT_SHARE;
675                break;
676            case LocalData.LOCAL_PHOTO_SPHERE:
677                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
678                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
679                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
680                break;
681            case LocalData.LOCAL_360_PHOTO_SPHERE:
682                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
683                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
684                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
685                        | SUPPORT_SHOW_ON_MAP;
686                break;
687            default:
688                break;
689        }
690
691        // In secure camera mode, we only support delete operation.
692        if (isSecureCamera()) {
693            supported &= SUPPORT_DELETE;
694        }
695
696        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
697                (supported & SUPPORT_DELETE) != 0);
698        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
699                (supported & SUPPORT_ROTATE) != 0);
700        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
701                (supported & SUPPORT_ROTATE) != 0);
702        setMenuItemVisible(mActionBarMenu, R.id.action_details,
703                (supported & SUPPORT_INFO) != 0);
704        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
705                (supported & SUPPORT_CROP) != 0);
706        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
707                (supported & SUPPORT_SETAS) != 0);
708        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
709                (supported & SUPPORT_EDIT) != 0);
710        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
711                (supported & SUPPORT_TRIM) != 0);
712
713        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
714        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
715        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
716        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
717
718        if (panoramaShare) {
719            // For 360 PhotoSphere, relegate standard share to the overflow menu
720            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
721            if (item != null) {
722                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
723                item.setTitle(getResources().getString(R.string.share_as_photo));
724            }
725            // And, promote "share as panorama" to action bar
726            item = mActionBarMenu.findItem(R.id.action_share_panorama);
727            if (item != null) {
728                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
729            }
730            setPanoramaShareIntent(currentData.getContentUri());
731        }
732        if (standardShare) {
733            if (!panoramaShare) {
734                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
735                if (item != null) {
736                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
737                    item.setTitle(getResources().getString(R.string.share));
738                }
739            }
740            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
741            setNfcBeamPushUri(currentData.getContentUri());
742        }
743
744        boolean itemHasLocation = currentData.getLatLong() != null;
745        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
746                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
747    }
748
749    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
750        MenuItem item = menu.findItem(itemId);
751        if (item != null) {
752            item.setVisible(visible);
753        }
754    }
755
756    private final ImageTaskManager.TaskListener mPlaceholderListener =
757            new ImageTaskManager.TaskListener() {
758
759                @Override
760                public void onTaskQueued(String filePath, final Uri imageUri) {
761                    mMainHandler.post(new Runnable() {
762                        @Override
763                        public void run() {
764                            notifyNewMedia(imageUri);
765                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
766                            if (dataID != -1) {
767                                LocalData d = mDataAdapter.getLocalData(dataID);
768                                InProgressDataWrapper newData = new InProgressDataWrapper(d, true);
769                                mDataAdapter.updateData(dataID, newData);
770                            }
771                        }
772                    });
773                }
774
775                @Override
776                public void onTaskDone(String filePath, final Uri imageUri) {
777                    mMainHandler.post(new Runnable() {
778                        @Override
779                        public void run() {
780                            mDataAdapter.refresh(getContentResolver(), imageUri);
781                        }
782                    });
783                }
784
785                @Override
786                public void onTaskProgress(String filePath, Uri imageUri, int progress) {
787                    // Do nothing
788                }
789            };
790
791    private final ImageTaskManager.TaskListener mStitchingListener =
792            new ImageTaskManager.TaskListener() {
793                @Override
794                public void onTaskQueued(String filePath, final Uri imageUri) {
795                    mMainHandler.post(new Runnable() {
796                        @Override
797                        public void run() {
798                            notifyNewMedia(imageUri);
799                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
800                            if (dataID != -1) {
801                                // Don't allow special UI actions (swipe to
802                                // delete, for example) on in-progress data.
803                                LocalData d = mDataAdapter.getLocalData(dataID);
804                                InProgressDataWrapper newData = new InProgressDataWrapper(d);
805                                mDataAdapter.updateData(dataID, newData);
806                            }
807                        }
808                    });
809                }
810
811                @Override
812                public void onTaskDone(String filePath, final Uri imageUri) {
813                    Log.v(TAG, "onTaskDone:" + filePath);
814                    mMainHandler.post(new Runnable() {
815                        @Override
816                        public void run() {
817                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
818                            int currentDataId = mFilmstripController.getCurrentId();
819
820                            if (currentDataId == doneID) {
821                                hidePanoStitchingProgress();
822                                updateStitchingProgress(0);
823                            }
824
825                            mDataAdapter.refresh(getContentResolver(), imageUri);
826                        }
827                    });
828                }
829
830                @Override
831                public void onTaskProgress(
832                        String filePath, final Uri imageUri, final int progress) {
833                    mMainHandler.post(new Runnable() {
834                        @Override
835                        public void run() {
836                            int currentDataId = mFilmstripController.getCurrentId();
837                            if (currentDataId == -1) {
838                                return;
839                            }
840                            if (imageUri.equals(
841                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
842                                updateStitchingProgress(progress);
843                            }
844                        }
845                    });
846                }
847            };
848
849    @Override
850    public Context getAndroidContext() {
851        return this;
852    }
853
854    @Override
855    public int getCurrentModuleIndex() {
856        return mCurrentModeIndex;
857    }
858
859    @Override
860    public SurfaceTexture getPreviewBuffer() {
861        // TODO: implement this
862        return null;
863    }
864
865    @Override
866    public FrameLayout getModuleLayoutRoot() {
867        return mCameraModuleRootView;
868    }
869
870    @Override
871    public void setShutterEventsListener(ShutterEventsListener listener) {
872        // TODO: implement this
873    }
874
875    @Override
876    public void setShutterEnabled(boolean enabled) {
877        // TODO: implement this
878    }
879
880    @Override
881    public boolean isShutterEnabled() {
882        // TODO: implement this
883        return false;
884    }
885
886    @Override
887    public void startPreCaptureAnimation() {
888        // TODO: implement this
889    }
890
891    @Override
892    public void cancelPreCaptureAnimation() {
893        // TODO: implement this
894    }
895
896    @Override
897    public void startPostCaptureAnimation() {
898        // TODO: implement this
899    }
900
901    @Override
902    public void startPostCaptureAnimation(Bitmap thumbnail) {
903        // TODO: implement this
904    }
905
906    @Override
907    public void cancelPostCaptureAnimation() {
908        // TODO: implement this
909    }
910
911    @Override
912    public OrientationManager getOrientationManager() {
913        return mOrientationManager;
914    }
915
916    @Override
917    public LocationManager getLocationManager() {
918        return mLocationManager;
919    }
920
921    @Override
922    public void lockOrientation() {
923        mOrientationManager.lockOrientation();
924    }
925
926    @Override
927    public void unlockOrientation() {
928        mOrientationManager.unlockOrientation();
929    }
930
931    @Override
932    public void notifyNewMedia(Uri uri) {
933        ContentResolver cr = getContentResolver();
934        String mimeType = cr.getType(uri);
935        if (mimeType.startsWith("video/")) {
936            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
937            mDataAdapter.addNewVideo(cr, uri);
938        } else if (mimeType.startsWith("image/")) {
939            CameraUtil.broadcastNewPicture(this, uri);
940            mDataAdapter.addNewPhoto(cr, uri);
941        } else if (mimeType.startsWith("application/stitching-preview")) {
942            mDataAdapter.addNewPhoto(cr, uri);
943        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
944            mDataAdapter.addNewPhoto(cr, uri);
945        } else {
946            android.util.Log.w(TAG, "Unknown new media with MIME type:"
947                    + mimeType + ", uri:" + uri);
948        }
949    }
950
951    @Override
952    public void enableKeepScreenOn(boolean enabled) {
953        if (mPaused) {
954            return;
955        }
956
957        mKeepScreenOn = enabled;
958        if (mKeepScreenOn) {
959            mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
960            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
961        } else {
962            keepScreenOnForAWhile();
963        }
964    }
965
966    @Override
967    public CameraProvider getCameraProvider() {
968        return mCameraController;
969    }
970
971    private void removeData(int dataID) {
972        mDataAdapter.removeData(CameraActivity.this, dataID);
973        if (mDataAdapter.getTotalNumber() > 1) {
974            showUndoDeletionBar();
975        } else {
976            // If camera preview is the only view left in filmstrip,
977            // no need to show undo bar.
978            mPendingDeletion = true;
979            performDeletion();
980        }
981    }
982
983
984    @Override
985    public boolean onCreateOptionsMenu(Menu menu) {
986        // Inflate the menu items for use in the action bar
987        MenuInflater inflater = getMenuInflater();
988        inflater.inflate(R.menu.operations, menu);
989        mActionBarMenu = menu;
990
991        // Configure the standard share action provider
992        MenuItem item = menu.findItem(R.id.action_share);
993        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
994        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
995        if (mStandardShareIntent != null) {
996            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
997        }
998
999        // Configure the panorama share action provider
1000        item = menu.findItem(R.id.action_share_panorama);
1001        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
1002        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
1003        if (mPanoramaShareIntent != null) {
1004            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
1005        }
1006
1007        mStandardShareActionProvider.setOnShareTargetSelectedListener(this);
1008        mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this);
1009
1010        return super.onCreateOptionsMenu(menu);
1011    }
1012
1013    @Override
1014    public boolean onOptionsItemSelected(MenuItem item) {
1015        int currentDataId = mFilmstripController.getCurrentId();
1016        if (currentDataId < 0) {
1017            return false;
1018        }
1019        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
1020
1021        // Handle presses on the action bar items
1022        switch (item.getItemId()) {
1023            case android.R.id.home:
1024                // ActionBar's Up/Home button was clicked
1025                try {
1026                    startActivity(IntentHelper.getGalleryIntent(this));
1027                    return true;
1028                } catch (ActivityNotFoundException e) {
1029                    Log.w(TAG, "Failed to launch gallery activity, closing");
1030                    finish();
1031                }
1032            case R.id.action_delete:
1033                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1034                        UsageStatistics.ACTION_DELETE, null, 0,
1035                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1036                removeData(currentDataId);
1037                return true;
1038            case R.id.action_edit:
1039                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1040                        UsageStatistics.ACTION_EDIT, null, 0,
1041                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1042                launchEditor(localData);
1043                return true;
1044            case R.id.action_trim: {
1045                // This is going to be handled by the Gallery app.
1046                Intent intent = new Intent(ACTION_TRIM_VIDEO);
1047                LocalData currentData = mDataAdapter.getLocalData(mFilmstripController.getCurrentId());
1048                intent.setData(currentData.getContentUri());
1049                // We need the file path to wrap this into a RandomAccessFile.
1050                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
1051                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1052                return true;
1053            }
1054            case R.id.action_rotate_ccw:
1055                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
1056                return true;
1057            case R.id.action_rotate_cw:
1058                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
1059                return true;
1060            case R.id.action_crop: {
1061                UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1062                        UsageStatistics.ACTION_CROP, null, 0,
1063                        UsageStatistics.hashFileName(fileNameFromDataID(currentDataId)));
1064                Intent intent = new Intent(CropActivity.CROP_ACTION);
1065                intent.setClass(this, CropActivity.class);
1066                intent.setDataAndType(localData.getContentUri(), localData.getMimeType())
1067                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1068                startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1069                return true;
1070            }
1071            case R.id.action_setas: {
1072                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
1073                        .setDataAndType(localData.getContentUri(),
1074                                localData.getMimeType())
1075                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1076                intent.putExtra("mimeType", intent.getType());
1077                startActivityForResult(Intent.createChooser(
1078                        intent, getString(R.string.set_as)), REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1079                return true;
1080            }
1081            case R.id.action_details:
1082                (new AsyncTask<Void, Void, MediaDetails>() {
1083                    @Override
1084                    protected MediaDetails doInBackground(Void... params) {
1085                        return localData.getMediaDetails(CameraActivity.this);
1086                    }
1087
1088                    @Override
1089                    protected void onPostExecute(MediaDetails mediaDetails) {
1090                        if (mediaDetails != null) {
1091                            DetailsDialog.create(CameraActivity.this, mediaDetails).show();
1092                        }
1093                    }
1094                }).execute();
1095                return true;
1096            case R.id.action_show_on_map:
1097                double[] latLong = localData.getLatLong();
1098                if (latLong != null) {
1099                    CameraUtil.showOnMap(this, latLong);
1100                }
1101                return true;
1102            default:
1103                return super.onOptionsItemSelected(item);
1104        }
1105    }
1106
1107    private boolean isCaptureIntent() {
1108        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
1109                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1110                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1111            return true;
1112        } else {
1113            return false;
1114        }
1115    }
1116
1117    @Override
1118    public void onCreate(Bundle state) {
1119        super.onCreate(state);
1120        GcamHelper.init(getContentResolver());
1121
1122        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
1123        setContentView(R.layout.activity_main);
1124        mActionBar = getActionBar();
1125        mActionBar.addOnMenuVisibilityListener(this);
1126        mMainHandler = new MainHandler(getMainLooper());
1127        mCameraController =
1128                new CameraController(this, this, mMainHandler,
1129                        CameraManagerFactory.getAndroidCameraManager());
1130        // TODO: Try to move all the resources allocation to happen as soon as
1131        // possible so we can call module.init() at the earliest time.
1132        mModuleManager = new ModuleManagerImpl();
1133        ModulesInfo.setupModules(this, mModuleManager);
1134
1135        mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
1136        if (ApiHelper.HAS_ROTATION_ANIMATION) {
1137            setRotationAnimation();
1138        }
1139
1140        // Check if this is in the secure camera mode.
1141        Intent intent = getIntent();
1142        String action = intent.getAction();
1143        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
1144                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
1145            mSecureCamera = true;
1146        } else {
1147            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
1148        }
1149
1150        if (mSecureCamera) {
1151            // Change the window flags so that secure camera can show when locked
1152            Window win = getWindow();
1153            WindowManager.LayoutParams params = win.getAttributes();
1154            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
1155            win.setAttributes(params);
1156
1157            // Filter for screen off so that we can finish secure camera activity
1158            // when screen is off.
1159            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
1160            registerReceiver(mScreenOffReceiver, filter);
1161        }
1162        mAboveFilmstripControlLayout =
1163                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
1164        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
1165        // Hide action bar first since we are in full screen mode first, and
1166        // switch the system UI to lights-out mode.
1167        this.setSystemBarsVisibility(false);
1168        mPanoramaManager = AppManagerFactory.getInstance(this)
1169                .getPanoramaStitchingManager();
1170        mPlaceholderManager = AppManagerFactory.getInstance(this)
1171                .getGcamProcessingManager();
1172        mPanoramaManager.addTaskListener(mStitchingListener);
1173        mPlaceholderManager.addTaskListener(mPlaceholderListener);
1174        LayoutInflater inflater = getLayoutInflater();
1175        View rootLayout = inflater.inflate(R.layout.camera, null, false);
1176        mCameraModuleRootView = (FrameLayout) rootLayout.findViewById(R.id.camera_app_root);
1177        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
1178        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
1179        mCameraPreviewData = new CameraPreviewData(rootLayout,
1180                FilmstripImageData.SIZE_FULL,
1181                FilmstripImageData.SIZE_FULL);
1182        // Put a CameraPreviewData at the first position.
1183        mWrappedDataAdapter = new FixedFirstDataAdapter(
1184                new CameraDataAdapter(new ColorDrawable(
1185                        getResources().getColor(R.color.photo_placeholder))),
1186                mCameraPreviewData);
1187        mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
1188        mFilmstripController.setViewGap(
1189                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
1190        mPanoramaViewHelper = new PanoramaViewHelper(this);
1191        mPanoramaViewHelper.onCreate();
1192        mFilmstripController.setPanoramaViewHelper(mPanoramaViewHelper);
1193        // Set up the camera preview first so the preview shows up ASAP.
1194        mFilmstripController.setListener(mFilmStripListener);
1195
1196        // TODO: Remove the 3rd parameter once mCameraModuleRoot is moved out of filmstrip
1197        mCameraAppUI = new CameraAppUI(this,
1198                (MainActivityLayout) findViewById(R.id.activity_root_view),
1199                mCameraModuleRootView,
1200                isSecureCamera(), isCaptureIntent());
1201
1202        mSettingsManager = new SettingsManager(this, null, mCameraController.getNumberOfCameras());
1203
1204        mLocationManager = new LocationManager(this,
1205            new LocationManager.Listener() {
1206                @Override
1207                public void showGpsOnScreenIndicator(boolean hasSignal) {
1208                }
1209
1210                @Override
1211                public void hideGpsOnScreenIndicator() {
1212                }
1213            });
1214
1215        mSettingsController = new SettingsController(this, mSettingsManager, mLocationManager);
1216
1217        int modeIndex = -1;
1218        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
1219                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
1220            modeIndex = ModeListView.MODE_VIDEO;
1221        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
1222                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
1223                        .getAction())) {
1224            modeIndex = ModeListView.MODE_PHOTO;
1225            if (mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX)
1226                        == ModeListView.MODE_GCAM && GcamHelper.hasGcamCapture()) {
1227                modeIndex = ModeListView.MODE_GCAM;
1228            }
1229        } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
1230                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
1231            modeIndex = ModeListView.MODE_PHOTO;
1232        } else {
1233            // If the activity has not been started using an explicit intent,
1234            // read the module index from the last time the user changed modes
1235            modeIndex = mSettingsManager.getInt(SettingsManager.SETTING_STARTUP_MODULE_INDEX);
1236            if ((modeIndex == ModeListView.MODE_GCAM &&
1237                    !GcamHelper.hasGcamCapture()) || modeIndex < 0) {
1238                modeIndex = ModeListView.MODE_PHOTO;
1239            }
1240        }
1241
1242        mOrientationManager = new OrientationManagerImpl(this);
1243        mOrientationManager.addOnOrientationChangeListener(mMainHandler, this);
1244
1245        setModuleFromModeIndex(modeIndex);
1246
1247        // TODO: Remove this when refactor is done.
1248        if (modeIndex == ModulesInfo.MODULE_PHOTO ||
1249                modeIndex == ModulesInfo.MODULE_VIDEO) {
1250            mCameraAppUI.prepareModuleUI();
1251        }
1252        mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1253
1254        if (!mSecureCamera) {
1255            mDataAdapter = mWrappedDataAdapter;
1256            mFilmstripController.setDataAdapter(mDataAdapter);
1257            if (!isCaptureIntent()) {
1258                mDataAdapter.requestLoad(getContentResolver());
1259            }
1260        } else {
1261            // Put a lock placeholder as the last image by setting its date to
1262            // 0.
1263            ImageView v = (ImageView) getLayoutInflater().inflate(
1264                    R.layout.secure_album_placeholder, null);
1265            v.setOnClickListener(new View.OnClickListener() {
1266                @Override
1267                public void onClick(View view) {
1268                    try {
1269                        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1270                                UsageStatistics.ACTION_GALLERY, null);
1271                        startActivity(IntentHelper.getGalleryIntent(CameraActivity.this));
1272                    } catch (ActivityNotFoundException e) {
1273                        Log.w(TAG, "Failed to launch gallery activity, closing");
1274                    }
1275                    finish();
1276                }
1277            });
1278            mDataAdapter = new FixedLastDataAdapter(
1279                    mWrappedDataAdapter,
1280                    new SimpleViewData(
1281                            v,
1282                            v.getDrawable().getIntrinsicWidth(),
1283                            v.getDrawable().getIntrinsicHeight(),
1284                            0, 0));
1285            // Flush out all the original data.
1286            mDataAdapter.flush();
1287            mFilmstripController.setDataAdapter(mDataAdapter);
1288        }
1289
1290        setupNfcBeamPush();
1291
1292        mLocalImagesObserver = new LocalMediaObserver();
1293        mLocalVideosObserver = new LocalMediaObserver();
1294
1295        getContentResolver().registerContentObserver(
1296                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true,
1297                mLocalImagesObserver);
1298        getContentResolver().registerContentObserver(
1299                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true,
1300                mLocalVideosObserver);
1301    }
1302
1303    private void setRotationAnimation() {
1304        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
1305        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
1306        Window win = getWindow();
1307        WindowManager.LayoutParams winParams = win.getAttributes();
1308        winParams.rotationAnimation = rotationAnimation;
1309        win.setAttributes(winParams);
1310    }
1311
1312    @Override
1313    public void onUserInteraction() {
1314        super.onUserInteraction();
1315        if (!isFinishing()) {
1316            keepScreenOnForAWhile();
1317        }
1318    }
1319
1320    @Override
1321    public boolean dispatchTouchEvent(MotionEvent ev) {
1322        boolean result = super.dispatchTouchEvent(ev);
1323        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1324            // Real deletion is postponed until the next user interaction after
1325            // the gesture that triggers deletion. Until real deletion is performed,
1326            // users can click the undo button to bring back the image that they
1327            // chose to delete.
1328            if (mPendingDeletion && !mIsUndoingDeletion) {
1329                performDeletion();
1330            }
1331        }
1332        return result;
1333    }
1334
1335    @Override
1336    public void onPause() {
1337        mPaused = true;
1338
1339        // Delete photos that are pending deletion
1340        performDeletion();
1341        // TODO: call mCurrentModule.pause() instead after all the modules
1342        // support pause().
1343        mCurrentModule.pause();
1344        mOrientationManager.pause();
1345        // Close the camera and wait for the operation done.
1346        mCameraController.closeCamera();
1347
1348        mLocalImagesObserver.setActivityPaused(true);
1349        mLocalVideosObserver.setActivityPaused(true);
1350        resetScreenOn();
1351        super.onPause();
1352    }
1353
1354    @Override
1355    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1356        if (requestCode == REQ_CODE_DONT_SWITCH_TO_PREVIEW) {
1357            mResetToPreviewOnResume = false;
1358        } else {
1359            super.onActivityResult(requestCode, resultCode, data);
1360        }
1361    }
1362
1363    @Override
1364    public void onResume() {
1365        mPaused = false;
1366
1367        // TODO: Handle this in OrientationManager.
1368        // Auto-rotate off
1369        if (Settings.System.getInt(getContentResolver(),
1370                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {
1371            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
1372            mAutoRotateScreen = false;
1373        } else {
1374            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
1375            mAutoRotateScreen = true;
1376        }
1377
1378        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
1379                UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName());
1380
1381        mOrientationManager.resume();
1382        super.onResume();
1383        mCurrentModule.resume();
1384
1385        setSwipingEnabled(true);
1386
1387        if (mResetToPreviewOnResume) {
1388            // Go to the preview on resume.
1389            mFilmstripController.goToFirstItem();
1390        }
1391        // Default is showing the preview, unless disabled by explicitly
1392        // starting an activity we want to return from to the filmstrip rather
1393        // than the preview.
1394        mResetToPreviewOnResume = true;
1395
1396        if (mLocalVideosObserver.isMediaDataChangedDuringPause()
1397                || mLocalImagesObserver.isMediaDataChangedDuringPause()) {
1398            if (!mSecureCamera) {
1399                // If it's secure camera, requestLoad() should not be called
1400                // as it will load all the data.
1401                mDataAdapter.requestLoad(getContentResolver());
1402            }
1403        }
1404        mLocalImagesObserver.setActivityPaused(false);
1405        mLocalVideosObserver.setActivityPaused(false);
1406
1407        keepScreenOnForAWhile();
1408
1409        mModeListView.startAccordionAnimation();
1410    }
1411
1412    @Override
1413    public void onStart() {
1414        super.onStart();
1415        mPanoramaViewHelper.onStart();
1416    }
1417
1418    @Override
1419    protected void onStop() {
1420        mPanoramaViewHelper.onStop();
1421
1422        CameraManagerFactory.recycle();
1423        super.onStop();
1424    }
1425
1426    @Override
1427    public void onDestroy() {
1428        if (mSecureCamera) {
1429            unregisterReceiver(mScreenOffReceiver);
1430        }
1431        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
1432        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
1433        super.onDestroy();
1434    }
1435
1436    @Override
1437    public void onConfigurationChanged(Configuration config) {
1438        super.onConfigurationChanged(config);
1439        mCurrentModule.onConfigurationChanged(config);
1440    }
1441
1442    @Override
1443    public boolean onKeyDown(int keyCode, KeyEvent event) {
1444        if (mFilmstripController.inCameraFullscreen()) {
1445            if (mCurrentModule.onKeyDown(keyCode, event)) {
1446                return true;
1447            }
1448            // Prevent software keyboard or voice search from showing up.
1449            if (keyCode == KeyEvent.KEYCODE_SEARCH
1450                    || keyCode == KeyEvent.KEYCODE_MENU) {
1451                if (event.isLongPress()) {
1452                    return true;
1453                }
1454            }
1455        }
1456
1457        return super.onKeyDown(keyCode, event);
1458    }
1459
1460    @Override
1461    public boolean onKeyUp(int keyCode, KeyEvent event) {
1462        if (mFilmstripController.inCameraFullscreen() && mCurrentModule.onKeyUp(keyCode, event)) {
1463            return true;
1464        }
1465        return super.onKeyUp(keyCode, event);
1466    }
1467
1468    @Override
1469    public void onBackPressed() {
1470        if (!mFilmstripController.inCameraFullscreen()) {
1471            mFilmstripController.goToFirstItem();
1472        } else if (!mCurrentModule.onBackPressed()) {
1473            super.onBackPressed();
1474        }
1475    }
1476
1477    public boolean isAutoRotateScreen() {
1478        return mAutoRotateScreen;
1479    }
1480
1481    protected void updateStorageSpace() {
1482        mStorageSpaceBytes = Storage.getAvailableSpace();
1483    }
1484
1485    protected long getStorageSpaceBytes() {
1486        return mStorageSpaceBytes;
1487    }
1488
1489    protected void updateStorageSpaceAndHint() {
1490        updateStorageSpace();
1491        updateStorageHint(mStorageSpaceBytes);
1492    }
1493
1494    protected void updateStorageHint(long storageSpace) {
1495        String message = null;
1496        if (storageSpace == Storage.UNAVAILABLE) {
1497            message = getString(R.string.no_storage);
1498        } else if (storageSpace == Storage.PREPARING) {
1499            message = getString(R.string.preparing_sd);
1500        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
1501            message = getString(R.string.access_sd_fail);
1502        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1503            message = getString(R.string.spaceIsLow_content);
1504        }
1505
1506        if (message != null) {
1507            if (mStorageHint == null) {
1508                mStorageHint = OnScreenHint.makeText(this, message);
1509            } else {
1510                mStorageHint.setText(message);
1511            }
1512            mStorageHint.show();
1513        } else if (mStorageHint != null) {
1514            mStorageHint.cancel();
1515            mStorageHint = null;
1516        }
1517    }
1518
1519    protected void setResultEx(int resultCode) {
1520        mResultCodeForTesting = resultCode;
1521        setResult(resultCode);
1522    }
1523
1524    protected void setResultEx(int resultCode, Intent data) {
1525        mResultCodeForTesting = resultCode;
1526        mResultDataForTesting = data;
1527        setResult(resultCode, data);
1528    }
1529
1530    public int getResultCode() {
1531        return mResultCodeForTesting;
1532    }
1533
1534    public Intent getResultData() {
1535        return mResultDataForTesting;
1536    }
1537
1538    public boolean isSecureCamera() {
1539        return mSecureCamera;
1540    }
1541
1542    @Override
1543    public boolean isPaused() {
1544        return mPaused;
1545    }
1546
1547    @Override
1548    public void onModeSelected(int modeIndex) {
1549        if (mCurrentModeIndex == modeIndex) {
1550            return;
1551        }
1552
1553        if (modeIndex == ModeListView.MODE_SETTING) {
1554            onSettingsSelected();
1555            return;
1556        }
1557
1558        CameraHolder.instance().keep();
1559        closeModule(mCurrentModule);
1560        int oldModuleIndex = mCurrentModeIndex;
1561        setModuleFromModeIndex(modeIndex);
1562
1563        // TODO: The following check is temporary for quick switch between video and photo.
1564        // When the refactor is done, similar logic will be applied to all modules.
1565        if (mCurrentModeIndex == ModulesInfo.MODULE_PHOTO
1566                || mCurrentModeIndex == ModulesInfo.MODULE_VIDEO) {
1567            if (oldModuleIndex != ModulesInfo.MODULE_PHOTO
1568                    && oldModuleIndex != ModulesInfo.MODULE_VIDEO) {
1569                mCameraAppUI.prepareModuleUI();
1570            } else {
1571                mCameraAppUI.clearModuleUI();
1572            }
1573        } else {
1574            // This is the old way of removing all views in CameraRootView. Will
1575            // be deprecated soon. It is here to make sure modules that haven't
1576            // been refactored can still function.
1577            mCameraAppUI.clearCameraUI();
1578        }
1579
1580        openModule(mCurrentModule);
1581        mCurrentModule.onOrientationChanged(mLastRawOrientation);
1582        if (mMediaSaver != null) {
1583            mCurrentModule.onMediaSaverAvailable(mMediaSaver);
1584        }
1585        // Store the module index so we can use it the next time the Camera
1586        // starts up.
1587        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
1588        prefs.edit().putInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, modeIndex).apply();
1589    }
1590
1591    public void onSettingsSelected() {
1592        // Temporary until we finalize the touch flow.
1593        LayoutInflater inflater = getLayoutInflater();
1594        SettingsView settingsView = (SettingsView) inflater.inflate(R.layout.settings_list_layout,
1595            null, false);
1596        settingsView.setSettingsListener(mSettingsController);
1597        PopupWindow popup = new PopupWindow(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1598        popup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1599        popup.setOutsideTouchable(true);
1600        popup.setFocusable(true);
1601        popup.setContentView(settingsView);
1602        popup.showAtLocation(mModeListView.getRootView(), Gravity.CENTER, 0, 0);
1603    }
1604
1605    /**
1606     * Sets the mCurrentModuleIndex, creates a new module instance for the given
1607     * index an sets it as mCurrentModule.
1608     */
1609    private void setModuleFromModeIndex(int modeIndex) {
1610        ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
1611        if (agent == null) {
1612            return;
1613        }
1614        if (!agent.requestAppForCamera()) {
1615            mCameraController.closeCamera();
1616        }
1617        mCurrentModeIndex = agent.getModuleId();
1618        mCurrentModule = (CameraModule)  agent.createModule(this);
1619    }
1620
1621    @Override
1622    public SettingsManager getSettingsManager() {
1623        return mSettingsManager;
1624    }
1625
1626    @Override
1627    public CameraServices getServices() {
1628        return (CameraServices) getApplication();
1629    }
1630
1631    @Override
1632    public SettingsController getSettingsController() {
1633        return mSettingsController;
1634    }
1635
1636    /**
1637     * Launches an ACTION_EDIT intent for the given local data item.
1638     */
1639    public void launchEditor(LocalData data) {
1640        Intent intent = new Intent(Intent.ACTION_EDIT)
1641                .setDataAndType(data.getContentUri(), data.getMimeType())
1642                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1643        try {
1644            startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1645        } catch (ActivityNotFoundException e) {
1646            startActivityForResult(Intent.createChooser(intent, null),
1647                    REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1648        }
1649    }
1650
1651    /**
1652     * Launch the tiny planet editor.
1653     *
1654     * @param data The data must be a 360 degree stereographically mapped
1655     *             panoramic image. It will not be modified, instead a new item
1656     *             with the result will be added to the filmstrip.
1657     */
1658    public void launchTinyPlanetEditor(LocalData data) {
1659        TinyPlanetFragment fragment = new TinyPlanetFragment();
1660        Bundle bundle = new Bundle();
1661        bundle.putString(TinyPlanetFragment.ARGUMENT_URI, data.getContentUri().toString());
1662        bundle.putString(TinyPlanetFragment.ARGUMENT_TITLE, data.getTitle());
1663        fragment.setArguments(bundle);
1664        fragment.show(getFragmentManager(), "tiny_planet");
1665    }
1666
1667    private void openModule(CameraModule module) {
1668        module.init(this, isSecureCamera(), isCaptureIntent());
1669        module.resume();
1670    }
1671
1672    private void closeModule(CameraModule module) {
1673        module.pause();
1674    }
1675
1676    private void performDeletion() {
1677        if (!mPendingDeletion) {
1678            return;
1679        }
1680        hideUndoDeletionBar(false);
1681        mDataAdapter.executeDeletion(CameraActivity.this);
1682
1683        int currentId = mFilmstripController.getCurrentId();
1684        updateActionBarMenu(currentId);
1685        mFilmStripListener.onCurrentDataCentered(currentId);
1686    }
1687
1688    public void showUndoDeletionBar() {
1689        if (mPendingDeletion) {
1690            performDeletion();
1691        }
1692        Log.v(TAG, "showing undo bar");
1693        mPendingDeletion = true;
1694        if (mUndoDeletionBar == null) {
1695            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(R.layout.undo_bar,
1696                    mAboveFilmstripControlLayout, true);
1697            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
1698            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
1699            button.setOnClickListener(new View.OnClickListener() {
1700                @Override
1701                public void onClick(View view) {
1702                    mDataAdapter.undoDataRemoval();
1703                    hideUndoDeletionBar(true);
1704                }
1705            });
1706            // Setting undo bar clickable to avoid touch events going through
1707            // the bar to the buttons (eg. edit button, etc) underneath the bar.
1708            mUndoDeletionBar.setClickable(true);
1709            // When there is user interaction going on with the undo button, we
1710            // do not want to hide the undo bar.
1711            button.setOnTouchListener(new View.OnTouchListener() {
1712                @Override
1713                public boolean onTouch(View v, MotionEvent event) {
1714                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
1715                        mIsUndoingDeletion = true;
1716                    } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
1717                        mIsUndoingDeletion = false;
1718                    }
1719                    return false;
1720                }
1721            });
1722        }
1723        mUndoDeletionBar.setAlpha(0f);
1724        mUndoDeletionBar.setVisibility(View.VISIBLE);
1725        mUndoDeletionBar.animate().setDuration(200).alpha(1f).setListener(null).start();
1726    }
1727
1728    private void hideUndoDeletionBar(boolean withAnimation) {
1729        Log.v(TAG, "Hiding undo deletion bar");
1730        mPendingDeletion = false;
1731        if (mUndoDeletionBar != null) {
1732            if (withAnimation) {
1733                mUndoDeletionBar.animate().setDuration(200).alpha(0f)
1734                        .setListener(new Animator.AnimatorListener() {
1735                            @Override
1736                            public void onAnimationStart(Animator animation) {
1737                                // Do nothing.
1738                            }
1739
1740                            @Override
1741                            public void onAnimationEnd(Animator animation) {
1742                                mUndoDeletionBar.setVisibility(View.GONE);
1743                            }
1744
1745                            @Override
1746                            public void onAnimationCancel(Animator animation) {
1747                                // Do nothing.
1748                            }
1749
1750                            @Override
1751                            public void onAnimationRepeat(Animator animation) {
1752                                // Do nothing.
1753                            }
1754                        }).start();
1755            } else {
1756                mUndoDeletionBar.setVisibility(View.GONE);
1757            }
1758        }
1759    }
1760
1761    @Override
1762    public void onOrientationChanged(int orientation) {
1763        // We keep the last known orientation. So if the user first orient
1764        // the camera then point the camera to floor or sky, we still have
1765        // the correct orientation.
1766        if (orientation == OrientationManager.ORIENTATION_UNKNOWN) {
1767            return;
1768        }
1769        mLastRawOrientation = orientation;
1770        if (mCurrentModule != null) {
1771            mCurrentModule.onOrientationChanged(orientation);
1772        }
1773    }
1774
1775    /**
1776     * Enable/disable swipe-to-filmstrip. Will always disable swipe if in
1777     * capture intent.
1778     *
1779     * @param enable {@code true} to enable swipe.
1780     */
1781    public void setSwipingEnabled(boolean enable) {
1782        if (isCaptureIntent()) {
1783            mCameraPreviewData.lockPreview(true);
1784        } else {
1785            mCameraPreviewData.lockPreview(!enable);
1786        }
1787    }
1788
1789
1790    /**
1791     * Check whether camera controls are visible.
1792     *
1793     * @return whether controls are visible.
1794     */
1795    private boolean arePreviewControlsVisible() {
1796        return mCurrentModule.arePreviewControlsVisible();
1797    }
1798
1799    /**
1800     * Show or hide the {@link CameraControls} using the current module's
1801     * implementation of {@link #onPreviewFocusChanged}.
1802     *
1803     * @param showControls whether to show camera controls.
1804     */
1805    private void setPreviewControlsVisibility(boolean showControls) {
1806        mCurrentModule.onPreviewFocusChanged(showControls);
1807    }
1808
1809    // Accessor methods for getting latency times used in performance testing
1810    public long getAutoFocusTime() {
1811        return (mCurrentModule instanceof PhotoModule) ?
1812                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1813    }
1814
1815    public long getShutterLag() {
1816        return (mCurrentModule instanceof PhotoModule) ?
1817                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1818    }
1819
1820    public long getShutterToPictureDisplayedTime() {
1821        return (mCurrentModule instanceof PhotoModule) ?
1822                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1823    }
1824
1825    public long getPictureDisplayedToJpegCallbackTime() {
1826        return (mCurrentModule instanceof PhotoModule) ?
1827                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1828    }
1829
1830    public long getJpegCallbackFinishTime() {
1831        return (mCurrentModule instanceof PhotoModule) ?
1832                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1833    }
1834
1835    public long getCaptureStartTime() {
1836        return (mCurrentModule instanceof PhotoModule) ?
1837                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1838    }
1839
1840    public boolean isRecording() {
1841        return (mCurrentModule instanceof VideoModule) ?
1842                ((VideoModule) mCurrentModule).isRecording() : false;
1843    }
1844
1845    public CameraManager.CameraOpenCallback getCameraOpenErrorCallback() {
1846        return mCameraController;
1847    }
1848
1849    // For debugging purposes only.
1850    public CameraModule getCurrentModule() {
1851        return mCurrentModule;
1852    }
1853
1854    private void keepScreenOnForAWhile() {
1855        if (mKeepScreenOn) {
1856            return;
1857        }
1858        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1859        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1860        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_ON_FLAG, SCREEN_DELAY_MS);
1861    }
1862
1863    private void resetScreenOn() {
1864        mKeepScreenOn = false;
1865        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_ON_FLAG);
1866        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1867    }
1868}
1869