Home | History | Annotate | Download | only in panorama
      1 /*
      2  * Copyright (C) 2011 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 
     17 package com.android.camera.panorama;
     18 
     19 import com.android.camera.ActivityBase;
     20 import com.android.camera.CameraDisabledException;
     21 import com.android.camera.CameraHardwareException;
     22 import com.android.camera.CameraHolder;
     23 import com.android.camera.Exif;
     24 import com.android.camera.MenuHelper;
     25 import com.android.camera.ModePicker;
     26 import com.android.camera.OnClickAttr;
     27 import com.android.camera.R;
     28 import com.android.camera.RotateDialogController;
     29 import com.android.camera.ShutterButton;
     30 import com.android.camera.Storage;
     31 import com.android.camera.Thumbnail;
     32 import com.android.camera.Util;
     33 import com.android.camera.ui.PopupManager;
     34 import com.android.camera.ui.Rotatable;
     35 import com.android.camera.ui.RotateImageView;
     36 import com.android.camera.ui.RotateLayout;
     37 import com.android.camera.ui.SharePopup;
     38 
     39 import android.content.ContentResolver;
     40 import android.content.Context;
     41 import android.content.res.AssetFileDescriptor;
     42 import android.content.pm.ActivityInfo;
     43 import android.content.res.Resources;
     44 import android.graphics.Bitmap;
     45 import android.graphics.BitmapFactory;
     46 import android.graphics.ImageFormat;
     47 import android.graphics.PixelFormat;
     48 import android.graphics.Rect;
     49 import android.graphics.SurfaceTexture;
     50 import android.graphics.YuvImage;
     51 import android.hardware.Camera.Parameters;
     52 import android.hardware.Camera.Size;
     53 import android.hardware.Sensor;
     54 import android.hardware.SensorManager;
     55 import android.media.ExifInterface;
     56 import android.media.MediaActionSound;
     57 import android.net.Uri;
     58 import android.os.Bundle;
     59 import android.os.Handler;
     60 import android.os.Message;
     61 import android.os.ParcelFileDescriptor;
     62 import android.os.PowerManager;
     63 import android.util.Log;
     64 import android.view.Gravity;
     65 import android.view.Menu;
     66 import android.view.MenuItem;
     67 import android.view.OrientationEventListener;
     68 import android.view.View;
     69 import android.view.ViewGroup;
     70 import android.view.Window;
     71 import android.view.WindowManager;
     72 import android.widget.ImageView;
     73 import android.widget.TextView;
     74 
     75 import java.io.ByteArrayOutputStream;
     76 import java.io.File;
     77 import java.io.IOException;
     78 import java.util.List;
     79 
     80 /**
     81  * Activity to handle panorama capturing.
     82  */
     83 public class PanoramaActivity extends ActivityBase implements
     84         ModePicker.OnModeChangeListener, SurfaceTexture.OnFrameAvailableListener,
     85         ShutterButton.OnShutterButtonListener,
     86         MosaicRendererSurfaceViewRenderer.MosaicSurfaceCreateListener {
     87     public static final int DEFAULT_SWEEP_ANGLE = 160;
     88     public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
     89     public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
     90 
     91     private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
     92     private static final int MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL = 2;
     93     private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 3;
     94     private static final int MSG_RESET_TO_PREVIEW = 4;
     95     private static final int MSG_CLEAR_SCREEN_DELAY = 5;
     96 
     97     private static final int SCREEN_DELAY = 2 * 60 * 1000;
     98 
     99     private static final String TAG = "PanoramaActivity";
    100     private static final int PREVIEW_STOPPED = 0;
    101     private static final int PREVIEW_ACTIVE = 1;
    102     private static final int CAPTURE_STATE_VIEWFINDER = 0;
    103     private static final int CAPTURE_STATE_MOSAIC = 1;
    104 
    105     // Speed is in unit of deg/sec
    106     private static final float PANNING_SPEED_THRESHOLD = 20f;
    107 
    108     // Ratio of nanosecond to second
    109     private static final float NS2S = 1.0f / 1000000000.0f;
    110 
    111     private boolean mPausing;
    112 
    113     private View mPanoLayout;
    114     private View mCaptureLayout;
    115     private View mReviewLayout;
    116     private ImageView mReview;
    117     private RotateLayout mCaptureIndicator;
    118     private PanoProgressBar mPanoProgressBar;
    119     private PanoProgressBar mSavingProgressBar;
    120     private View mFastIndicationBorder;
    121     private View mLeftIndicator;
    122     private View mRightIndicator;
    123     private MosaicRendererSurfaceView mMosaicView;
    124     private TextView mTooFastPrompt;
    125     private ShutterButton mShutterButton;
    126     private Object mWaitObject = new Object();
    127 
    128     private String mPreparePreviewString;
    129     private String mDialogTitle;
    130     private String mDialogOkString;
    131     private String mDialogPanoramaFailedString;
    132 
    133     private int mIndicatorColor;
    134     private int mIndicatorColorFast;
    135 
    136     private float mCompassValueX;
    137     private float mCompassValueY;
    138     private float mCompassValueXStart;
    139     private float mCompassValueYStart;
    140     private float mCompassValueXStartBuffer;
    141     private float mCompassValueYStartBuffer;
    142     private int mCompassThreshold;
    143     private int mTraversedAngleX;
    144     private int mTraversedAngleY;
    145     private long mTimestamp;
    146 
    147     private RotateImageView mThumbnailView;
    148     private Thumbnail mThumbnail;
    149     private SharePopup mSharePopup;
    150 
    151     private int mPreviewWidth;
    152     private int mPreviewHeight;
    153     private int mCameraState;
    154     private int mCaptureState;
    155     private SensorManager mSensorManager;
    156     private Sensor mSensor;
    157     private PowerManager.WakeLock mPartialWakeLock;
    158     private ModePicker mModePicker;
    159     private MosaicFrameProcessor mMosaicFrameProcessor;
    160     private long mTimeTaken;
    161     private Handler mMainHandler;
    162     private SurfaceTexture mSurfaceTexture;
    163     private boolean mThreadRunning;
    164     private boolean mCancelComputation;
    165     private float[] mTransformMatrix;
    166     private float mHorizontalViewAngle;
    167     private float mVerticalViewAngle;
    168 
    169     // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
    170     // getting a better image quality by the former.
    171     private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
    172 
    173     private PanoOrientationEventListener mOrientationEventListener;
    174     // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
    175     // respectively.
    176     private int mDeviceOrientation;
    177     private int mDeviceOrientationAtCapture;
    178     private int mCameraOrientation;
    179     private int mOrientationCompensation;
    180 
    181     private RotateDialogController mRotateDialog;
    182 
    183     private MediaActionSound mCameraSound;
    184 
    185     private class MosaicJpeg {
    186         public MosaicJpeg(byte[] data, int width, int height) {
    187             this.data = data;
    188             this.width = width;
    189             this.height = height;
    190             this.isValid = true;
    191         }
    192 
    193         public MosaicJpeg() {
    194             this.data = null;
    195             this.width = 0;
    196             this.height = 0;
    197             this.isValid = false;
    198         }
    199 
    200         public final byte[] data;
    201         public final int width;
    202         public final int height;
    203         public final boolean isValid;
    204     }
    205 
    206     private class PanoOrientationEventListener extends OrientationEventListener {
    207         public PanoOrientationEventListener(Context context) {
    208             super(context);
    209         }
    210 
    211         @Override
    212         public void onOrientationChanged(int orientation) {
    213             // We keep the last known orientation. So if the user first orient
    214             // the camera then point the camera to floor or sky, we still have
    215             // the correct orientation.
    216             if (orientation == ORIENTATION_UNKNOWN) return;
    217             mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation);
    218             // When the screen is unlocked, display rotation may change. Always
    219             // calculate the up-to-date orientationCompensation.
    220             int orientationCompensation = mDeviceOrientation
    221                     + Util.getDisplayRotation(PanoramaActivity.this);
    222             if (mOrientationCompensation != orientationCompensation) {
    223                 mOrientationCompensation = orientationCompensation;
    224                 setOrientationIndicator(mOrientationCompensation);
    225             }
    226         }
    227     }
    228 
    229     private void setOrientationIndicator(int degree) {
    230         if (mSharePopup != null) mSharePopup.setOrientation(degree);
    231     }
    232 
    233     @Override
    234     public boolean onCreateOptionsMenu(Menu menu) {
    235         super.onCreateOptionsMenu(menu);
    236 
    237         addBaseMenuItems(menu);
    238         return true;
    239     }
    240 
    241     @Override
    242     public boolean onPrepareOptionsMenu(Menu menu) {
    243         super.onPrepareOptionsMenu(menu);
    244         // Only show the menu when idle.
    245         boolean idle = (mCaptureState == CAPTURE_STATE_VIEWFINDER && !mThreadRunning);
    246         for (int i = 0; i < menu.size(); i++) {
    247             MenuItem item = menu.getItem(i);
    248             item.setVisible(idle);
    249             item.setEnabled(idle);
    250         }
    251 
    252         return true;
    253     }
    254 
    255     private void addBaseMenuItems(Menu menu) {
    256         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_CAMERA, new Runnable() {
    257             public void run() {
    258                 switchToOtherMode(ModePicker.MODE_CAMERA);
    259             }
    260         });
    261         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_VIDEO, new Runnable() {
    262             public void run() {
    263                 switchToOtherMode(ModePicker.MODE_VIDEO);
    264             }
    265         });
    266     }
    267 
    268     @Override
    269     public void onCreate(Bundle icicle) {
    270         super.onCreate(icicle);
    271 
    272         Window window = getWindow();
    273         Util.enterLightsOutMode(window);
    274         Util.initializeScreenBrightness(window, getContentResolver());
    275 
    276         createContentView();
    277 
    278         mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    279         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
    280         if (mSensor == null) {
    281             mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    282         }
    283         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    284         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
    285 
    286         mOrientationEventListener = new PanoOrientationEventListener(this);
    287 
    288         mTransformMatrix = new float[16];
    289 
    290         mPreparePreviewString =
    291                 getResources().getString(R.string.pano_dialog_prepare_preview);
    292         mDialogTitle = getResources().getString(R.string.pano_dialog_title);
    293         mDialogOkString = getResources().getString(R.string.dialog_ok);
    294         mDialogPanoramaFailedString =
    295                 getResources().getString(R.string.pano_dialog_panorama_failed);
    296         mCameraSound = new MediaActionSound();
    297 
    298         mMainHandler = new Handler() {
    299             @Override
    300             public void handleMessage(Message msg) {
    301                 switch (msg.what) {
    302                     case MSG_LOW_RES_FINAL_MOSAIC_READY:
    303                         onBackgroundThreadFinished();
    304                         showFinalMosaic((Bitmap) msg.obj);
    305                         saveHighResMosaic();
    306                         break;
    307                     case MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL:
    308                         onBackgroundThreadFinished();
    309                         // If the activity is paused, save the thumbnail to the file here.
    310                         // If not, it will be saved in onPause.
    311                         if (mPausing) saveThumbnailToFile();
    312                         // Set the thumbnail bitmap here because mThumbnailView must be accessed
    313                         // from the UI thread.
    314                         updateThumbnailButton();
    315 
    316                         // Share popup may still have the reference to the old thumbnail. Clear it.
    317                         mSharePopup = null;
    318                         resetToPreview();
    319                         break;
    320                     case MSG_GENERATE_FINAL_MOSAIC_ERROR:
    321                         onBackgroundThreadFinished();
    322                         if (mPausing) {
    323                             resetToPreview();
    324                         } else {
    325                             mRotateDialog.showAlertDialog(
    326                                     mDialogTitle, mDialogPanoramaFailedString,
    327                                     mDialogOkString, new Runnable() {
    328                                         @Override
    329                                         public void run() {
    330                                             resetToPreview();
    331                                         }},
    332                                     null, null);
    333                         }
    334                         break;
    335                     case MSG_RESET_TO_PREVIEW:
    336                         onBackgroundThreadFinished();
    337                         resetToPreview();
    338                         break;
    339                     case MSG_CLEAR_SCREEN_DELAY:
    340                         getWindow().clearFlags(WindowManager.LayoutParams.
    341                                 FLAG_KEEP_SCREEN_ON);
    342                         break;
    343                 }
    344                 clearMosaicFrameProcessorIfNeeded();
    345             }
    346         };
    347     }
    348 
    349     private void setupCamera() throws CameraHardwareException, CameraDisabledException {
    350         openCamera();
    351         Parameters parameters = mCameraDevice.getParameters();
    352         setupCaptureParams(parameters);
    353         configureCamera(parameters);
    354     }
    355 
    356     private void releaseCamera() {
    357         if (mCameraDevice != null) {
    358             mCameraDevice.setPreviewCallbackWithBuffer(null);
    359             CameraHolder.instance().release();
    360             mCameraDevice = null;
    361             mCameraState = PREVIEW_STOPPED;
    362         }
    363     }
    364 
    365     private void openCamera() throws CameraHardwareException, CameraDisabledException {
    366         int backCameraId = CameraHolder.instance().getBackCameraId();
    367         mCameraDevice = Util.openCamera(this, backCameraId);
    368         mCameraOrientation = Util.getCameraOrientation(backCameraId);
    369     }
    370 
    371     private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
    372             boolean needSmaller) {
    373         int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
    374         boolean hasFound = false;
    375         for (Size size : supportedSizes) {
    376             int h = size.height;
    377             int w = size.width;
    378             // we only want 4:3 format.
    379             int d = DEFAULT_CAPTURE_PIXELS - h * w;
    380             if (needSmaller && d < 0) { // no bigger preview than 960x720.
    381                 continue;
    382             }
    383             if (need4To3 && (h * 4 != w * 3)) {
    384                 continue;
    385             }
    386             d = Math.abs(d);
    387             if (d < pixelsDiff) {
    388                 mPreviewWidth = w;
    389                 mPreviewHeight = h;
    390                 pixelsDiff = d;
    391                 hasFound = true;
    392             }
    393         }
    394         return hasFound;
    395     }
    396 
    397     private void setupCaptureParams(Parameters parameters) {
    398         List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
    399         if (!findBestPreviewSize(supportedSizes, true, true)) {
    400             Log.w(TAG, "No 4:3 ratio preview size supported.");
    401             if (!findBestPreviewSize(supportedSizes, false, true)) {
    402                 Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
    403                 findBestPreviewSize(supportedSizes, false, false);
    404             }
    405         }
    406         Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
    407         parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
    408 
    409         List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
    410         int last = frameRates.size() - 1;
    411         int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
    412         int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
    413         parameters.setPreviewFpsRange(minFps, maxFps);
    414         Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
    415 
    416         List<String> supportedFocusModes = parameters.getSupportedFocusModes();
    417         if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
    418             parameters.setFocusMode(mTargetFocusMode);
    419         } else {
    420             // Use the default focus mode and log a message
    421             Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
    422                   " becuase the mode is not supported.");
    423         }
    424 
    425         parameters.setRecordingHint(false);
    426 
    427         mHorizontalViewAngle = parameters.getHorizontalViewAngle();
    428         mVerticalViewAngle =  parameters.getVerticalViewAngle();
    429     }
    430 
    431     public int getPreviewBufSize() {
    432         PixelFormat pixelInfo = new PixelFormat();
    433         PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
    434         // TODO: remove this extra 32 byte after the driver bug is fixed.
    435         return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
    436     }
    437 
    438     private void configureCamera(Parameters parameters) {
    439         mCameraDevice.setParameters(parameters);
    440     }
    441 
    442     private boolean switchToOtherMode(int mode) {
    443         if (isFinishing()) {
    444             return false;
    445         }
    446         MenuHelper.gotoMode(mode, this);
    447         finish();
    448         return true;
    449     }
    450 
    451     public boolean onModeChanged(int mode) {
    452         if (mode != ModePicker.MODE_PANORAMA) {
    453             return switchToOtherMode(mode);
    454         } else {
    455             return true;
    456         }
    457     }
    458 
    459     @Override
    460     public void onMosaicSurfaceChanged() {
    461         runOnUiThread(new Runnable() {
    462             @Override
    463             public void run() {
    464                 // If panorama is generating low res or high res mosaic, it
    465                 // means users exit and come back to panorama. Do not start the
    466                 // preview. Preview will be started after final mosaic is
    467                 // generated.
    468                 if (!mPausing && !mThreadRunning) {
    469                     startCameraPreview();
    470                 }
    471             }
    472         });
    473     }
    474 
    475     @Override
    476     public void onMosaicSurfaceCreated(final int textureID) {
    477         runOnUiThread(new Runnable() {
    478             @Override
    479             public void run() {
    480                 if (mSurfaceTexture != null) {
    481                     mSurfaceTexture.release();
    482                 }
    483                 mSurfaceTexture = new SurfaceTexture(textureID);
    484                 if (!mPausing) {
    485                     mSurfaceTexture.setOnFrameAvailableListener(PanoramaActivity.this);
    486                 }
    487             }
    488         });
    489     }
    490 
    491     public void runViewFinder() {
    492         mMosaicView.setWarping(false);
    493         // Call preprocess to render it to low-res and high-res RGB textures.
    494         mMosaicView.preprocess(mTransformMatrix);
    495         mMosaicView.setReady();
    496         mMosaicView.requestRender();
    497     }
    498 
    499     public void runMosaicCapture() {
    500         mMosaicView.setWarping(true);
    501         // Call preprocess to render it to low-res and high-res RGB textures.
    502         mMosaicView.preprocess(mTransformMatrix);
    503         // Lock the conditional variable to ensure the order of transferGPUtoCPU and
    504         // mMosaicFrame.processFrame().
    505         mMosaicView.lockPreviewReadyFlag();
    506         // Now, transfer the textures from GPU to CPU memory for processing
    507         mMosaicView.transferGPUtoCPU();
    508         // Wait on the condition variable (will be opened when GPU->CPU transfer is done).
    509         mMosaicView.waitUntilPreviewReady();
    510         mMosaicFrameProcessor.processFrame();
    511     }
    512 
    513     public synchronized void onFrameAvailable(SurfaceTexture surface) {
    514         /* This function may be called by some random thread,
    515          * so let's be safe and use synchronize. No OpenGL calls can be done here.
    516          */
    517         // Frames might still be available after the activity is paused. If we call onFrameAvailable
    518         // after pausing, the GL thread will crash.
    519         if (mPausing) return;
    520 
    521         // Updating the texture should be done in the GL thread which mMosaicView is attached.
    522         mMosaicView.queueEvent(new Runnable() {
    523             @Override
    524             public void run() {
    525                 // Check if the activity is paused here can speed up the onPause() process.
    526                 if (mPausing) return;
    527                 mSurfaceTexture.updateTexImage();
    528                 mSurfaceTexture.getTransformMatrix(mTransformMatrix);
    529             }
    530         });
    531         // Update the transformation matrix for mosaic pre-process.
    532         if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
    533             runViewFinder();
    534         } else {
    535             runMosaicCapture();
    536         }
    537     }
    538 
    539     private void hideDirectionIndicators() {
    540         mLeftIndicator.setVisibility(View.GONE);
    541         mRightIndicator.setVisibility(View.GONE);
    542     }
    543 
    544     private void showDirectionIndicators(int direction) {
    545         switch (direction) {
    546             case PanoProgressBar.DIRECTION_NONE:
    547                 mLeftIndicator.setVisibility(View.VISIBLE);
    548                 mRightIndicator.setVisibility(View.VISIBLE);
    549                 break;
    550             case PanoProgressBar.DIRECTION_LEFT:
    551                 mLeftIndicator.setVisibility(View.VISIBLE);
    552                 mRightIndicator.setVisibility(View.GONE);
    553                 break;
    554             case PanoProgressBar.DIRECTION_RIGHT:
    555                 mLeftIndicator.setVisibility(View.GONE);
    556                 mRightIndicator.setVisibility(View.VISIBLE);
    557                 break;
    558         }
    559     }
    560 
    561     public void startCapture() {
    562         // Reset values so we can do this again.
    563         mCancelComputation = false;
    564         mTimeTaken = System.currentTimeMillis();
    565         mCaptureState = CAPTURE_STATE_MOSAIC;
    566         mShutterButton.setBackgroundResource(R.drawable.btn_shutter_pan_recording);
    567         mCaptureIndicator.setVisibility(View.VISIBLE);
    568         showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
    569         mThumbnailView.setEnabled(false);
    570 
    571         mCompassValueXStart = mCompassValueXStartBuffer;
    572         mCompassValueYStart = mCompassValueYStartBuffer;
    573         mTimestamp = 0;
    574 
    575         mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
    576             @Override
    577             public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
    578                     float progressX, float progressY) {
    579                 float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
    580                 float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
    581                 if (isFinished
    582                         || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
    583                         || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
    584                     stopCapture(false);
    585                 } else {
    586                     float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
    587                     float panningRateYInDegree = panningRateY * mVerticalViewAngle;
    588                     updateProgress(panningRateXInDegree, panningRateYInDegree,
    589                             accumulatedHorizontalAngle, accumulatedVerticalAngle);
    590                 }
    591             }
    592         });
    593 
    594         if (mModePicker != null) mModePicker.setEnabled(false);
    595 
    596         mPanoProgressBar.reset();
    597         // TODO: calculate the indicator width according to different devices to reflect the actual
    598         // angle of view of the camera device.
    599         mPanoProgressBar.setIndicatorWidth(20);
    600         mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE);
    601         mPanoProgressBar.setVisibility(View.VISIBLE);
    602         mDeviceOrientationAtCapture = mDeviceOrientation;
    603         keepScreenOn();
    604     }
    605 
    606     private void stopCapture(boolean aborted) {
    607         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    608         mCaptureIndicator.setVisibility(View.GONE);
    609         hideTooFastIndication();
    610         hideDirectionIndicators();
    611         mThumbnailView.setEnabled(true);
    612 
    613         mMosaicFrameProcessor.setProgressListener(null);
    614         stopCameraPreview();
    615 
    616         mSurfaceTexture.setOnFrameAvailableListener(null);
    617 
    618         if (!aborted && !mThreadRunning) {
    619             mRotateDialog.showWaitingDialog(mPreparePreviewString);
    620             runBackgroundThread(new Thread() {
    621                 @Override
    622                 public void run() {
    623                     MosaicJpeg jpeg = generateFinalMosaic(false);
    624 
    625                     if (jpeg != null && jpeg.isValid) {
    626                         Bitmap bitmap = null;
    627                         bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
    628                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    629                                 MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
    630                     } else {
    631                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    632                                 MSG_RESET_TO_PREVIEW));
    633                     }
    634                 }
    635             });
    636         }
    637         // do we have to wait for the thread to complete before enabling this?
    638         if (mModePicker != null) mModePicker.setEnabled(true);
    639         keepScreenOnAwhile();
    640     }
    641 
    642     private void showTooFastIndication() {
    643         mTooFastPrompt.setVisibility(View.VISIBLE);
    644         mFastIndicationBorder.setVisibility(View.VISIBLE);
    645         mPanoProgressBar.setIndicatorColor(mIndicatorColorFast);
    646         mLeftIndicator.setEnabled(true);
    647         mRightIndicator.setEnabled(true);
    648     }
    649 
    650     private void hideTooFastIndication() {
    651         mTooFastPrompt.setVisibility(View.GONE);
    652         mFastIndicationBorder.setVisibility(View.GONE);
    653         mPanoProgressBar.setIndicatorColor(mIndicatorColor);
    654         mLeftIndicator.setEnabled(false);
    655         mRightIndicator.setEnabled(false);
    656     }
    657 
    658     private void updateProgress(float panningRateXInDegree, float panningRateYInDegree,
    659             float progressHorizontalAngle, float progressVerticalAngle) {
    660         mMosaicView.setReady();
    661         mMosaicView.requestRender();
    662 
    663         // TODO: Now we just display warning message by the panning speed.
    664         // Since we only support horizontal panning, we should display a warning message
    665         // in UI when there're significant vertical movements.
    666         if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD)
    667             || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) {
    668             showTooFastIndication();
    669         } else {
    670             hideTooFastIndication();
    671         }
    672         int angleInMajorDirection =
    673                 (Math.abs(progressHorizontalAngle) > Math.abs(progressVerticalAngle))
    674                 ? (int) progressHorizontalAngle
    675                 : (int) progressVerticalAngle;
    676         mPanoProgressBar.setProgress((angleInMajorDirection));
    677     }
    678 
    679     private void createContentView() {
    680         setContentView(R.layout.panorama);
    681 
    682         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    683 
    684         Resources appRes = getResources();
    685 
    686         mCaptureLayout = (View) findViewById(R.id.pano_capture_layout);
    687         mPanoProgressBar = (PanoProgressBar) findViewById(R.id.pano_pan_progress_bar);
    688         mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
    689         mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
    690         mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
    691         mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
    692         mPanoProgressBar.setIndicatorColor(mIndicatorColor);
    693         mPanoProgressBar.setOnDirectionChangeListener(
    694                 new PanoProgressBar.OnDirectionChangeListener () {
    695                     @Override
    696                     public void onDirectionChange(int direction) {
    697                         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
    698                             showDirectionIndicators(direction);
    699                         }
    700                     }
    701                 });
    702 
    703         mLeftIndicator = (ImageView) findViewById(R.id.pano_pan_left_indicator);
    704         mRightIndicator = (ImageView) findViewById(R.id.pano_pan_right_indicator);
    705         mLeftIndicator.setEnabled(false);
    706         mRightIndicator.setEnabled(false);
    707         mTooFastPrompt = (TextView) findViewById(R.id.pano_capture_too_fast_textview);
    708         mFastIndicationBorder = (View) findViewById(R.id.pano_speed_indication_border);
    709 
    710         mSavingProgressBar = (PanoProgressBar) findViewById(R.id.pano_saving_progress_bar);
    711         mSavingProgressBar.setIndicatorWidth(0);
    712         mSavingProgressBar.setMaxProgress(100);
    713         mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
    714         mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
    715 
    716         mCaptureIndicator = (RotateLayout) findViewById(R.id.pano_capture_indicator);
    717 
    718         mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
    719         mThumbnailView.enableFilter(false);
    720 
    721         mReviewLayout = (View) findViewById(R.id.pano_review_layout);
    722         mReview = (ImageView) findViewById(R.id.pano_reviewarea);
    723         mMosaicView = (MosaicRendererSurfaceView) findViewById(R.id.pano_renderer);
    724         mMosaicView.getRenderer().setMosaicSurfaceCreateListener(this);
    725 
    726         mModePicker = (ModePicker) findViewById(R.id.mode_picker);
    727         mModePicker.setVisibility(View.VISIBLE);
    728         mModePicker.setOnModeChangeListener(this);
    729         mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA);
    730 
    731         mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
    732         mShutterButton.setBackgroundResource(R.drawable.btn_shutter_pan);
    733         mShutterButton.setOnShutterButtonListener(this);
    734 
    735         mPanoLayout = findViewById(R.id.pano_layout);
    736 
    737         mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
    738 
    739         if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
    740             Rotatable[] rotateLayout = {
    741                     (Rotatable) findViewById(R.id.pano_pan_progress_bar_layout),
    742                     (Rotatable) findViewById(R.id.pano_capture_too_fast_textview_layout),
    743                     (Rotatable) findViewById(R.id.pano_review_saving_indication_layout),
    744                     (Rotatable) findViewById(R.id.pano_saving_progress_bar_layout),
    745                     (Rotatable) findViewById(R.id.pano_review_cancel_button_layout),
    746                     (Rotatable) findViewById(R.id.pano_rotate_reviewarea),
    747                     (Rotatable) mRotateDialog,
    748                     (Rotatable) mCaptureIndicator,
    749                     (Rotatable) mModePicker,
    750                     (Rotatable) mThumbnailView};
    751             for (Rotatable r : rotateLayout) {
    752                 r.setOrientation(270);
    753             }
    754         }
    755     }
    756 
    757     @Override
    758     public void onShutterButtonClick() {
    759         // If mSurfaceTexture == null then GL setup is not finished yet.
    760         // No buttons can be pressed.
    761         if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
    762         // Since this button will stay on the screen when capturing, we need to check the state
    763         // right now.
    764         switch (mCaptureState) {
    765             case CAPTURE_STATE_VIEWFINDER:
    766                 mCameraSound.play(MediaActionSound.START_VIDEO_RECORDING);
    767                 startCapture();
    768                 break;
    769             case CAPTURE_STATE_MOSAIC:
    770                 mCameraSound.play(MediaActionSound.STOP_VIDEO_RECORDING);
    771                 stopCapture(false);
    772         }
    773     }
    774 
    775     @Override
    776     public void onShutterButtonFocus(boolean pressed) {
    777     }
    778 
    779     public void reportProgress() {
    780         mSavingProgressBar.reset();
    781         mSavingProgressBar.setRightIncreasing(true);
    782         Thread t = new Thread() {
    783             @Override
    784             public void run() {
    785                 while (mThreadRunning) {
    786                     final int progress = mMosaicFrameProcessor.reportProgress(
    787                             true, mCancelComputation);
    788 
    789                     try {
    790                         synchronized (mWaitObject) {
    791                             mWaitObject.wait(50);
    792                         }
    793                     } catch (InterruptedException e) {
    794                         throw new RuntimeException("Panorama reportProgress failed", e);
    795                     }
    796                     // Update the progress bar
    797                     runOnUiThread(new Runnable() {
    798                         public void run() {
    799                             mSavingProgressBar.setProgress(progress);
    800                         }
    801                     });
    802                 }
    803             }
    804         };
    805         t.start();
    806     }
    807 
    808     private void initThumbnailButton() {
    809         // Load the thumbnail from the disk.
    810         if (mThumbnail == null) {
    811             mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
    812         }
    813         updateThumbnailButton();
    814     }
    815 
    816     private void updateThumbnailButton() {
    817         // Update last image if URI is invalid and the storage is ready.
    818         ContentResolver contentResolver = getContentResolver();
    819         if ((mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), contentResolver))) {
    820             mThumbnail = Thumbnail.getLastThumbnail(contentResolver);
    821         }
    822         if (mThumbnail != null) {
    823             mThumbnailView.setBitmap(mThumbnail.getBitmap());
    824         } else {
    825             mThumbnailView.setBitmap(null);
    826         }
    827     }
    828 
    829     private void saveThumbnailToFile() {
    830         if (mThumbnail != null && !mThumbnail.fromFile()) {
    831             mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
    832         }
    833     }
    834 
    835     public void saveHighResMosaic() {
    836         runBackgroundThread(new Thread() {
    837             @Override
    838             public void run() {
    839                 mPartialWakeLock.acquire();
    840                 MosaicJpeg jpeg;
    841                 try {
    842                     jpeg = generateFinalMosaic(true);
    843                 } finally {
    844                     mPartialWakeLock.release();
    845                 }
    846 
    847                 if (jpeg == null) {  // Cancelled by user.
    848                     mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
    849                 } else if (!jpeg.isValid) {  // Error when generating mosaic.
    850                     mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
    851                 } else {
    852                     // The panorama image returned from the library is orientated based on the
    853                     // natural orientation of a camera. We need to set an orientation for the image
    854                     // in its EXIF header, so the image can be displayed correctly.
    855                     // The orientation is calculated from compensating the
    856                     // device orientation at capture and the camera orientation respective to
    857                     // the natural orientation of the device.
    858                     int orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
    859                     Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
    860                     if (uri != null) {
    861                         // Create a thumbnail whose width or height is equal or bigger
    862                         // than the screen's width or height.
    863                         int widthRatio = (int) Math.ceil((double) jpeg.width
    864                                 / mPanoLayout.getWidth());
    865                         int heightRatio = (int) Math.ceil((double) jpeg.height
    866                                 / mPanoLayout.getHeight());
    867                         int inSampleSize = Integer.highestOneBit(
    868                                 Math.max(widthRatio, heightRatio));
    869                         mThumbnail = Thumbnail.createThumbnail(
    870                                 jpeg.data, orientation, inSampleSize, uri);
    871                         Util.broadcastNewPicture(PanoramaActivity.this, uri);
    872                     }
    873                     mMainHandler.sendMessage(
    874                             mMainHandler.obtainMessage(MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL));
    875                 }
    876             }
    877         });
    878         reportProgress();
    879     }
    880 
    881     private void runBackgroundThread(Thread thread) {
    882         mThreadRunning = true;
    883         thread.start();
    884     }
    885 
    886     private void onBackgroundThreadFinished() {
    887         mThreadRunning = false;
    888         mRotateDialog.dismissDialog();
    889     }
    890 
    891     private void cancelHighResComputation() {
    892         mCancelComputation = true;
    893         synchronized (mWaitObject) {
    894             mWaitObject.notify();
    895         }
    896     }
    897 
    898     @OnClickAttr
    899     public void onCancelButtonClicked(View v) {
    900         if (mPausing || mSurfaceTexture == null) return;
    901         cancelHighResComputation();
    902     }
    903 
    904     @OnClickAttr
    905     public void onThumbnailClicked(View v) {
    906         if (mPausing || mThreadRunning || mSurfaceTexture == null) return;
    907         showSharePopup();
    908     }
    909 
    910     private void showSharePopup() {
    911         if (mThumbnail == null) return;
    912         Uri uri = mThumbnail.getUri();
    913         if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
    914             // The orientation compensation is set to 0 here because we only support landscape.
    915             mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
    916                     mOrientationCompensation,
    917                     findViewById(R.id.frame_layout));
    918         }
    919         mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
    920     }
    921 
    922     private void reset() {
    923         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    924 
    925         mReviewLayout.setVisibility(View.GONE);
    926         mShutterButton.setBackgroundResource(R.drawable.btn_shutter_pan);
    927         mPanoProgressBar.setVisibility(View.GONE);
    928         mCaptureLayout.setVisibility(View.VISIBLE);
    929         mMosaicFrameProcessor.reset();
    930 
    931         mSurfaceTexture.setOnFrameAvailableListener(this);
    932     }
    933 
    934     private void resetToPreview() {
    935         reset();
    936         if (!mPausing) startCameraPreview();
    937     }
    938 
    939     private void showFinalMosaic(Bitmap bitmap) {
    940         if (bitmap != null) {
    941             mReview.setImageBitmap(bitmap);
    942         }
    943         mCaptureLayout.setVisibility(View.GONE);
    944         mReviewLayout.setVisibility(View.VISIBLE);
    945     }
    946 
    947     private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
    948         if (jpegData != null) {
    949             String filename = PanoUtil.createName(
    950                     getResources().getString(R.string.pano_file_name_format), mTimeTaken);
    951             Uri uri = Storage.addImage(getContentResolver(), filename, mTimeTaken, null,
    952                     orientation, jpegData, width, height);
    953             if (uri != null && orientation != 0) {
    954                 String filepath = Storage.generateFilepath(filename);
    955                 try {
    956                     // Save the orientation in EXIF.
    957                     ExifInterface exif = new ExifInterface(filepath);
    958                     exif.setAttribute(ExifInterface.TAG_ORIENTATION,
    959                             getExifOrientation(orientation));
    960                     exif.saveAttributes();
    961                 } catch (IOException e) {
    962                     Log.e(TAG, "cannot set exif data: " + filepath);
    963                 }
    964             }
    965             return uri;
    966         }
    967         return null;
    968     }
    969 
    970     private static String getExifOrientation(int orientation) {
    971         switch (orientation) {
    972             case 0:
    973                 return String.valueOf(ExifInterface.ORIENTATION_NORMAL);
    974             case 90:
    975                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
    976             case 180:
    977                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
    978             case 270:
    979                 return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
    980             default:
    981                 throw new AssertionError("invalid: " + orientation);
    982         }
    983     }
    984 
    985     private void clearMosaicFrameProcessorIfNeeded() {
    986         if (!mPausing || mThreadRunning) return;
    987         mMosaicFrameProcessor.clear();
    988     }
    989 
    990     private void initMosaicFrameProcessorIfNeeded() {
    991         if (mPausing || mThreadRunning) return;
    992         if (mMosaicFrameProcessor == null) {
    993             // Start the activity for the first time.
    994             mMosaicFrameProcessor = new MosaicFrameProcessor(
    995                     mPreviewWidth, mPreviewHeight, getPreviewBufSize());
    996         }
    997         mMosaicFrameProcessor.initialize();
    998     }
    999 
   1000     @Override
   1001     protected void onPause() {
   1002         super.onPause();
   1003 
   1004         mPausing = true;
   1005         // Stop the capturing first.
   1006         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
   1007             stopCapture(true);
   1008             reset();
   1009         }
   1010         if (mSharePopup != null) mSharePopup.dismiss();
   1011 
   1012         saveThumbnailToFile();
   1013 
   1014         releaseCamera();
   1015         mMosaicView.onPause();
   1016         clearMosaicFrameProcessorIfNeeded();
   1017         mOrientationEventListener.disable();
   1018         resetScreenOn();
   1019         mCameraSound.release();
   1020         System.gc();
   1021     }
   1022 
   1023     @Override
   1024     protected void doOnResume() {
   1025         mPausing = false;
   1026         mOrientationEventListener.enable();
   1027 
   1028         mCaptureState = CAPTURE_STATE_VIEWFINDER;
   1029         try {
   1030             setupCamera();
   1031 
   1032             // Camera must be initialized before MosaicFrameProcessor is initialized.
   1033             // The preview size has to be decided by camera device.
   1034             initMosaicFrameProcessorIfNeeded();
   1035             mMosaicView.onResume();
   1036 
   1037             initThumbnailButton();
   1038             keepScreenOnAwhile();
   1039         } catch (CameraHardwareException e) {
   1040             Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
   1041             return;
   1042         } catch (CameraDisabledException e) {
   1043             Util.showErrorAndFinish(this, R.string.camera_disabled);
   1044             return;
   1045         }
   1046         // Dismiss open menu if exists.
   1047         PopupManager.getInstance(this).notifyShowPopup(null);
   1048     }
   1049 
   1050     /**
   1051      * Generate the final mosaic image.
   1052      *
   1053      * @param highRes flag to indicate whether we want to get a high-res version.
   1054      * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
   1055      *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
   1056      *         is an error in generating the final mosaic.
   1057      */
   1058     public MosaicJpeg generateFinalMosaic(boolean highRes) {
   1059         int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
   1060         if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
   1061             return null;
   1062         } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
   1063             return new MosaicJpeg();
   1064         }
   1065 
   1066         byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
   1067         if (imageData == null) {
   1068             Log.e(TAG, "getFinalMosaicNV21() returned null.");
   1069             return new MosaicJpeg();
   1070         }
   1071 
   1072         int len = imageData.length - 8;
   1073         int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
   1074                 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
   1075         int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
   1076                 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
   1077         Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
   1078 
   1079         if (width <= 0 || height <= 0) {
   1080             // TODO: pop up a error meesage indicating that the final result is not generated.
   1081             Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
   1082                     height);
   1083             return new MosaicJpeg();
   1084         }
   1085 
   1086         YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
   1087         ByteArrayOutputStream out = new ByteArrayOutputStream();
   1088         yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
   1089         try {
   1090             out.close();
   1091         } catch (Exception e) {
   1092             Log.e(TAG, "Exception in storing final mosaic", e);
   1093             return new MosaicJpeg();
   1094         }
   1095         return new MosaicJpeg(out.toByteArray(), width, height);
   1096     }
   1097 
   1098     private void setPreviewTexture(SurfaceTexture surface) {
   1099         try {
   1100             mCameraDevice.setPreviewTexture(surface);
   1101         } catch (Throwable ex) {
   1102             releaseCamera();
   1103             throw new RuntimeException("setPreviewTexture failed", ex);
   1104         }
   1105     }
   1106 
   1107     private void startCameraPreview() {
   1108         // If we're previewing already, stop the preview first (this will blank
   1109         // the screen).
   1110         if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
   1111 
   1112         // Set the display orientation to 0, so that the underlying mosaic library
   1113         // can always get undistorted mPreviewWidth x mPreviewHeight image data
   1114         // from SurfaceTexture.
   1115         mCameraDevice.setDisplayOrientation(0);
   1116 
   1117         setPreviewTexture(mSurfaceTexture);
   1118 
   1119         try {
   1120             Log.v(TAG, "startPreview");
   1121             mCameraDevice.startPreview();
   1122         } catch (Throwable ex) {
   1123             releaseCamera();
   1124             throw new RuntimeException("startPreview failed", ex);
   1125         }
   1126         mCameraState = PREVIEW_ACTIVE;
   1127     }
   1128 
   1129     private void stopCameraPreview() {
   1130         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
   1131             Log.v(TAG, "stopPreview");
   1132             mCameraDevice.stopPreview();
   1133         }
   1134         mCameraState = PREVIEW_STOPPED;
   1135     }
   1136 
   1137     @Override
   1138     public void onUserInteraction() {
   1139         super.onUserInteraction();
   1140         if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
   1141     }
   1142 
   1143     @Override
   1144     public void onBackPressed() {
   1145         // If panorama is generating low res or high res mosaic, ignore back
   1146         // key. So the activity will not be destroyed.
   1147         if (mThreadRunning) return;
   1148         super.onBackPressed();
   1149     }
   1150 
   1151     private void resetScreenOn() {
   1152         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1153         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1154     }
   1155 
   1156     private void keepScreenOnAwhile() {
   1157         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1158         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1159         mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1160     }
   1161 
   1162     private void keepScreenOn() {
   1163         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1164         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1165     }
   1166 }
   1167