Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2009 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;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.graphics.Bitmap;
     24 import android.graphics.BitmapFactory;
     25 import android.graphics.Rect;
     26 import android.hardware.Camera.Parameters;
     27 import android.net.Uri;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.Message;
     31 import android.view.KeyEvent;
     32 import android.view.View;
     33 import android.view.Window;
     34 import android.view.WindowManager;
     35 import android.view.animation.AlphaAnimation;
     36 import android.view.animation.Animation;
     37 import android.view.animation.DecelerateInterpolator;
     38 
     39 import com.android.camera.ui.LayoutChangeNotifier;
     40 import com.android.camera.ui.PopupManager;
     41 import com.android.gallery3d.app.AbstractGalleryActivity;
     42 import com.android.gallery3d.app.AppBridge;
     43 import com.android.gallery3d.app.GalleryActionBar;
     44 import com.android.gallery3d.app.PhotoPage;
     45 import com.android.gallery3d.common.ApiHelper;
     46 import com.android.gallery3d.ui.ScreenNail;
     47 import com.android.gallery3d.util.MediaSetUtils;
     48 
     49 /**
     50  * Superclass of camera activity.
     51  */
     52 public abstract class ActivityBase extends AbstractGalleryActivity
     53         implements LayoutChangeNotifier.Listener {
     54 
     55     private static final String TAG = "ActivityBase";
     56     private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
     57     private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
     58             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
     59     public static final String ACTION_IMAGE_CAPTURE_SECURE =
     60             "android.media.action.IMAGE_CAPTURE_SECURE";
     61     // The intent extra for camera from secure lock screen. True if the gallery
     62     // should only show newly captured pictures. sSecureAlbumId does not
     63     // increment. This is used when switching between camera, camcorder, and
     64     // panorama. If the extra is not set, it is in the normal camera mode.
     65     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
     66 
     67     private int mResultCodeForTesting;
     68     private Intent mResultDataForTesting;
     69     private OnScreenHint mStorageHint;
     70     private View mSingleTapArea;
     71 
     72     protected boolean mOpenCameraFail;
     73     protected boolean mCameraDisabled;
     74     protected CameraManager.CameraProxy mCameraDevice;
     75     protected Parameters mParameters;
     76     // The activity is paused. The classes that extend this class should set
     77     // mPaused the first thing in onResume/onPause.
     78     protected boolean mPaused;
     79     protected GalleryActionBar mActionBar;
     80 
     81     // multiple cameras support
     82     protected int mNumberOfCameras;
     83     protected int mCameraId;
     84     // The activity is going to switch to the specified camera id. This is
     85     // needed because texture copy is done in GL thread. -1 means camera is not
     86     // switching.
     87     protected int mPendingSwitchCameraId = -1;
     88 
     89     protected MyAppBridge mAppBridge;
     90     protected ScreenNail mCameraScreenNail; // This shows camera preview.
     91     // The view containing only camera related widgets like control panel,
     92     // indicator bar, focus indicator and etc.
     93     protected View mCameraAppView;
     94     protected boolean mShowCameraAppView = true;
     95     private Animation mCameraAppViewFadeIn;
     96     private Animation mCameraAppViewFadeOut;
     97     // Secure album id. This should be incremented every time the camera is
     98     // launched from the secure lock screen. The id should be the same when
     99     // switching between camera, camcorder, and panorama.
    100     protected static int sSecureAlbumId;
    101     // True if the camera is started from secure lock screen.
    102     protected boolean mSecureCamera;
    103     private static boolean sFirstStartAfterScreenOn = true;
    104 
    105     private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
    106     private static final int UPDATE_STORAGE_HINT = 0;
    107     private final Handler mHandler = new Handler() {
    108             @Override
    109             public void handleMessage(Message msg) {
    110                 switch (msg.what) {
    111                     case UPDATE_STORAGE_HINT:
    112                         updateStorageHint();
    113                         return;
    114                 }
    115             }
    116     };
    117 
    118     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    119         @Override
    120         public void onReceive(Context context, Intent intent) {
    121             String action = intent.getAction();
    122             if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
    123                     || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
    124                     || action.equals(Intent.ACTION_MEDIA_CHECKING)
    125                     || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
    126                 updateStorageSpaceAndHint();
    127             }
    128         }
    129     };
    130 
    131     // close activity when screen turns off
    132     private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
    133         @Override
    134         public void onReceive(Context context, Intent intent) {
    135             finish();
    136         }
    137     };
    138 
    139     private static BroadcastReceiver sScreenOffReceiver;
    140     private static class ScreenOffReceiver extends BroadcastReceiver {
    141         @Override
    142         public void onReceive(Context context, Intent intent) {
    143             sFirstStartAfterScreenOn = true;
    144         }
    145     }
    146 
    147     public static boolean isFirstStartAfterScreenOn() {
    148         return sFirstStartAfterScreenOn;
    149     }
    150 
    151     public static void resetFirstStartAfterScreenOn() {
    152         sFirstStartAfterScreenOn = false;
    153     }
    154 
    155     protected class CameraOpenThread extends Thread {
    156         @Override
    157         public void run() {
    158             try {
    159                 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
    160                 mParameters = mCameraDevice.getParameters();
    161             } catch (CameraHardwareException e) {
    162                 mOpenCameraFail = true;
    163             } catch (CameraDisabledException e) {
    164                 mCameraDisabled = true;
    165             }
    166         }
    167     }
    168 
    169     @Override
    170     public void onCreate(Bundle icicle) {
    171         super.disableToggleStatusBar();
    172         // Set a theme with action bar. It is not specified in manifest because
    173         // we want to hide it by default. setTheme must happen before
    174         // setContentView.
    175         //
    176         // This must be set before we call super.onCreate(), where the window's
    177         // background is removed.
    178         setTheme(R.style.Theme_Gallery);
    179         getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    180         if (ApiHelper.HAS_ACTION_BAR) {
    181             requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
    182         } else {
    183             requestWindowFeature(Window.FEATURE_NO_TITLE);
    184         }
    185 
    186         // Check if this is in the secure camera mode.
    187         Intent intent = getIntent();
    188         String action = intent.getAction();
    189         if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
    190             mSecureCamera = true;
    191             // Use a new album when this is started from the lock screen.
    192             sSecureAlbumId++;
    193         } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
    194             mSecureCamera = true;
    195         } else {
    196             mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
    197         }
    198         if (mSecureCamera) {
    199             IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
    200             registerReceiver(mScreenOffReceiver, filter);
    201             if (sScreenOffReceiver == null) {
    202                 sScreenOffReceiver = new ScreenOffReceiver();
    203                 getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
    204             }
    205         }
    206         super.onCreate(icicle);
    207     }
    208 
    209     public boolean isPanoramaActivity() {
    210         return false;
    211     }
    212 
    213     @Override
    214     protected void onResume() {
    215         super.onResume();
    216 
    217         installIntentFilter();
    218         if (updateStorageHintOnResume()) {
    219             updateStorageSpace();
    220             mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200);
    221         }
    222     }
    223 
    224     @Override
    225     protected void onPause() {
    226         super.onPause();
    227 
    228         if (mStorageHint != null) {
    229             mStorageHint.cancel();
    230             mStorageHint = null;
    231         }
    232 
    233         unregisterReceiver(mReceiver);
    234     }
    235 
    236     @Override
    237     public void setContentView(int layoutResID) {
    238         super.setContentView(layoutResID);
    239         // getActionBar() should be after setContentView
    240         mActionBar = new GalleryActionBar(this);
    241         mActionBar.hide();
    242     }
    243 
    244     @Override
    245     public boolean onSearchRequested() {
    246         return false;
    247     }
    248 
    249     @Override
    250     public boolean onKeyDown(int keyCode, KeyEvent event) {
    251         // Prevent software keyboard or voice search from showing up.
    252         if (keyCode == KeyEvent.KEYCODE_SEARCH
    253                 || keyCode == KeyEvent.KEYCODE_MENU) {
    254             if (event.isLongPress()) return true;
    255         }
    256         if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
    257             return true;
    258         }
    259 
    260         return super.onKeyDown(keyCode, event);
    261     }
    262 
    263     @Override
    264     public boolean onKeyUp(int keyCode, KeyEvent event) {
    265         if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
    266             return true;
    267         }
    268         return super.onKeyUp(keyCode, event);
    269     }
    270 
    271     protected void setResultEx(int resultCode) {
    272         mResultCodeForTesting = resultCode;
    273         setResult(resultCode);
    274     }
    275 
    276     protected void setResultEx(int resultCode, Intent data) {
    277         mResultCodeForTesting = resultCode;
    278         mResultDataForTesting = data;
    279         setResult(resultCode, data);
    280     }
    281 
    282     public int getResultCode() {
    283         return mResultCodeForTesting;
    284     }
    285 
    286     public Intent getResultData() {
    287         return mResultDataForTesting;
    288     }
    289 
    290     @Override
    291     protected void onDestroy() {
    292         PopupManager.removeInstance(this);
    293         if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
    294         super.onDestroy();
    295     }
    296 
    297     protected void installIntentFilter() {
    298         // install an intent filter to receive SD card related events.
    299         IntentFilter intentFilter =
    300                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
    301         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    302         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    303         intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
    304         intentFilter.addDataScheme("file");
    305         registerReceiver(mReceiver, intentFilter);
    306     }
    307 
    308     protected void updateStorageSpace() {
    309         mStorageSpace = Storage.getAvailableSpace();
    310     }
    311 
    312     protected long getStorageSpace() {
    313         return mStorageSpace;
    314     }
    315 
    316     protected void updateStorageSpaceAndHint() {
    317         updateStorageSpace();
    318         updateStorageHint(mStorageSpace);
    319     }
    320 
    321     protected void updateStorageHint() {
    322         updateStorageHint(mStorageSpace);
    323     }
    324 
    325     protected boolean updateStorageHintOnResume() {
    326         return true;
    327     }
    328 
    329     protected void updateStorageHint(long storageSpace) {
    330         String message = null;
    331         if (storageSpace == Storage.UNAVAILABLE) {
    332             message = getString(R.string.no_storage);
    333         } else if (storageSpace == Storage.PREPARING) {
    334             message = getString(R.string.preparing_sd);
    335         } else if (storageSpace == Storage.UNKNOWN_SIZE) {
    336             message = getString(R.string.access_sd_fail);
    337         } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
    338             message = getString(R.string.spaceIsLow_content);
    339         }
    340 
    341         if (message != null) {
    342             if (mStorageHint == null) {
    343                 mStorageHint = OnScreenHint.makeText(this, message);
    344             } else {
    345                 mStorageHint.setText(message);
    346             }
    347             mStorageHint.show();
    348         } else if (mStorageHint != null) {
    349             mStorageHint.cancel();
    350             mStorageHint = null;
    351         }
    352     }
    353 
    354     protected void gotoGallery() {
    355         // Move the next picture with capture animation. "1" means next.
    356         mAppBridge.switchWithCaptureAnimation(1);
    357     }
    358 
    359     // Call this after setContentView.
    360     public ScreenNail createCameraScreenNail(boolean getPictures) {
    361         mCameraAppView = findViewById(R.id.camera_app_root);
    362         Bundle data = new Bundle();
    363         String path;
    364         if (getPictures) {
    365             if (mSecureCamera) {
    366                 path = "/secure/all/" + sSecureAlbumId;
    367             } else {
    368                 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
    369             }
    370         } else {
    371             path = "/local/all/0"; // Use 0 so gallery does not show anything.
    372         }
    373         data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
    374         data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
    375         data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
    376 
    377         // Send an AppBridge to gallery to enable the camera preview.
    378         if (mAppBridge != null) {
    379             mCameraScreenNail.recycle();
    380         }
    381         mAppBridge = new MyAppBridge();
    382         data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
    383         if (getStateManager().getStateCount() == 0) {
    384             getStateManager().startState(PhotoPage.class, data);
    385         } else {
    386             getStateManager().switchState(getStateManager().getTopState(),
    387                     PhotoPage.class, data);
    388         }
    389         mCameraScreenNail = mAppBridge.getCameraScreenNail();
    390         return mCameraScreenNail;
    391     }
    392 
    393     // Call this after setContentView.
    394     protected ScreenNail reuseCameraScreenNail(boolean getPictures) {
    395         mCameraAppView = findViewById(R.id.camera_app_root);
    396         Bundle data = new Bundle();
    397         String path;
    398         if (getPictures) {
    399             if (mSecureCamera) {
    400                 path = "/secure/all/" + sSecureAlbumId;
    401             } else {
    402                 path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
    403             }
    404         } else {
    405             path = "/local/all/0"; // Use 0 so gallery does not show anything.
    406         }
    407         data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
    408         data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
    409         data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
    410 
    411         // Send an AppBridge to gallery to enable the camera preview.
    412         if (mAppBridge == null) {
    413             mAppBridge = new MyAppBridge();
    414         }
    415         data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
    416         if (getStateManager().getStateCount() == 0) {
    417             getStateManager().startState(PhotoPage.class, data);
    418         }
    419         mCameraScreenNail = mAppBridge.getCameraScreenNail();
    420         return mCameraScreenNail;
    421     }
    422 
    423     private class HideCameraAppView implements Animation.AnimationListener {
    424         @Override
    425         public void onAnimationEnd(Animation animation) {
    426             // We cannot set this as GONE because we want to receive the
    427             // onLayoutChange() callback even when we are invisible.
    428             mCameraAppView.setVisibility(View.INVISIBLE);
    429         }
    430 
    431         @Override
    432         public void onAnimationRepeat(Animation animation) {
    433         }
    434 
    435         @Override
    436         public void onAnimationStart(Animation animation) {
    437         }
    438     }
    439 
    440     protected void updateCameraAppView() {
    441         // Initialize the animation.
    442         if (mCameraAppViewFadeIn == null) {
    443             mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f);
    444             mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
    445             mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator());
    446 
    447             mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f);
    448             mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
    449             mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator());
    450             mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView());
    451         }
    452 
    453         if (mShowCameraAppView) {
    454             mCameraAppView.setVisibility(View.VISIBLE);
    455             // The "transparent region" is not recomputed when a sibling of
    456             // SurfaceView changes visibility (unless it involves GONE). It's
    457             // been broken since 1.0. Call requestLayout to work around it.
    458             mCameraAppView.requestLayout();
    459             mCameraAppView.startAnimation(mCameraAppViewFadeIn);
    460         } else {
    461             mCameraAppView.startAnimation(mCameraAppViewFadeOut);
    462         }
    463     }
    464 
    465     protected void onFullScreenChanged(boolean full) {
    466         if (mShowCameraAppView == full) return;
    467         mShowCameraAppView = full;
    468         if (mPaused || isFinishing()) return;
    469         updateCameraAppView();
    470     }
    471 
    472     @Override
    473     public GalleryActionBar getGalleryActionBar() {
    474         return mActionBar;
    475     }
    476 
    477     // Preview frame layout has changed.
    478     @Override
    479     public void onLayoutChange(View v, int left, int top, int right, int bottom) {
    480         if (mAppBridge == null) return;
    481 
    482         int width = right - left;
    483         int height = bottom - top;
    484         if (ApiHelper.HAS_SURFACE_TEXTURE) {
    485             CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail;
    486             if (Util.getDisplayRotation(this) % 180 == 0) {
    487                 screenNail.setPreviewFrameLayoutSize(width, height);
    488             } else {
    489                 // Swap the width and height. Camera screen nail draw() is based on
    490                 // natural orientation, not the view system orientation.
    491                 screenNail.setPreviewFrameLayoutSize(height, width);
    492             }
    493             notifyScreenNailChanged();
    494         }
    495     }
    496 
    497     protected void setSingleTapUpListener(View singleTapArea) {
    498         mSingleTapArea = singleTapArea;
    499     }
    500 
    501     private boolean onSingleTapUp(int x, int y) {
    502         // Ignore if listener is null or the camera control is invisible.
    503         if (mSingleTapArea == null || !mShowCameraAppView) return false;
    504 
    505         int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
    506                 mSingleTapArea);
    507         x -= relativeLocation[0];
    508         y -= relativeLocation[1];
    509         if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
    510                 && y < mSingleTapArea.getHeight()) {
    511             onSingleTapUp(mSingleTapArea, x, y);
    512             return true;
    513         }
    514         return false;
    515     }
    516 
    517     protected void onSingleTapUp(View view, int x, int y) {
    518     }
    519 
    520     public void setSwipingEnabled(boolean enabled) {
    521         mAppBridge.setSwipingEnabled(enabled);
    522     }
    523 
    524     public void notifyScreenNailChanged() {
    525         mAppBridge.notifyScreenNailChanged();
    526     }
    527 
    528     protected void onPreviewTextureCopied() {
    529     }
    530 
    531     protected void onCaptureTextureCopied() {
    532     }
    533 
    534     protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) {
    535         if (mSecureCamera) {
    536             int id = Integer.parseInt(uri.getLastPathSegment());
    537             mAppBridge.addSecureAlbumItem(isVideo, id);
    538         }
    539     }
    540 
    541     public boolean isSecureCamera() {
    542         return mSecureCamera;
    543     }
    544 
    545     //////////////////////////////////////////////////////////////////////////
    546     //  The is the communication interface between the Camera Application and
    547     //  the Gallery PhotoPage.
    548     //////////////////////////////////////////////////////////////////////////
    549 
    550     class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
    551         @SuppressWarnings("hiding")
    552         private ScreenNail mCameraScreenNail;
    553         private Server mServer;
    554 
    555         @Override
    556         public ScreenNail attachScreenNail() {
    557             if (mCameraScreenNail == null) {
    558                 if (ApiHelper.HAS_SURFACE_TEXTURE) {
    559                     mCameraScreenNail = new CameraScreenNail(this);
    560                 } else {
    561                     Bitmap b = BitmapFactory.decodeResource(getResources(),
    562                             R.drawable.wallpaper_picker_preview);
    563                     mCameraScreenNail = new StaticBitmapScreenNail(b);
    564                 }
    565             }
    566             return mCameraScreenNail;
    567         }
    568 
    569         @Override
    570         public void detachScreenNail() {
    571             mCameraScreenNail = null;
    572         }
    573 
    574         public ScreenNail getCameraScreenNail() {
    575             return mCameraScreenNail;
    576         }
    577 
    578         // Return true if the tap is consumed.
    579         @Override
    580         public boolean onSingleTapUp(int x, int y) {
    581             return ActivityBase.this.onSingleTapUp(x, y);
    582         }
    583 
    584         // This is used to notify that the screen nail will be drawn in full screen
    585         // or not in next draw() call.
    586         @Override
    587         public void onFullScreenChanged(boolean full) {
    588             ActivityBase.this.onFullScreenChanged(full);
    589         }
    590 
    591         @Override
    592         public void requestRender() {
    593             getGLRoot().requestRenderForced();
    594         }
    595 
    596         @Override
    597         public void onPreviewTextureCopied() {
    598             ActivityBase.this.onPreviewTextureCopied();
    599         }
    600 
    601         @Override
    602         public void onCaptureTextureCopied() {
    603             ActivityBase.this.onCaptureTextureCopied();
    604         }
    605 
    606         @Override
    607         public void setServer(Server s) {
    608             mServer = s;
    609         }
    610 
    611         @Override
    612         public boolean isPanorama() {
    613             return ActivityBase.this.isPanoramaActivity();
    614         }
    615 
    616         @Override
    617         public boolean isStaticCamera() {
    618             return !ApiHelper.HAS_SURFACE_TEXTURE;
    619         }
    620 
    621         public void addSecureAlbumItem(boolean isVideo, int id) {
    622             if (mServer != null) mServer.addSecureAlbumItem(isVideo, id);
    623         }
    624 
    625         private void setCameraRelativeFrame(Rect frame) {
    626             if (mServer != null) mServer.setCameraRelativeFrame(frame);
    627         }
    628 
    629         private void switchWithCaptureAnimation(int offset) {
    630             if (mServer != null) mServer.switchWithCaptureAnimation(offset);
    631         }
    632 
    633         private void setSwipingEnabled(boolean enabled) {
    634             if (mServer != null) mServer.setSwipingEnabled(enabled);
    635         }
    636 
    637         private void notifyScreenNailChanged() {
    638             if (mServer != null) mServer.notifyScreenNailChanged();
    639         }
    640     }
    641 }
    642