OSDN Git Service

Merge "Use gallery icon for action bar app icon in camera." into jb-dev
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / app / AlbumPage.java
1 /*
2  * Copyright (C) 2010 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.gallery3d.app;
18
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.Rect;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.os.Vibrator;
28 import android.provider.MediaStore;
29 import android.view.ActionMode;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.widget.Toast;
34
35 import com.android.gallery3d.R;
36 import com.android.gallery3d.common.Utils;
37 import com.android.gallery3d.data.DataManager;
38 import com.android.gallery3d.data.MediaDetails;
39 import com.android.gallery3d.data.MediaItem;
40 import com.android.gallery3d.data.MediaObject;
41 import com.android.gallery3d.data.MediaSet;
42 import com.android.gallery3d.data.MtpDevice;
43 import com.android.gallery3d.data.Path;
44 import com.android.gallery3d.ui.ActionModeHandler;
45 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
46 import com.android.gallery3d.ui.AlbumSlotRenderer;
47 import com.android.gallery3d.ui.DetailsHelper;
48 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
49 import com.android.gallery3d.ui.FadeTexture;
50 import com.android.gallery3d.ui.GLCanvas;
51 import com.android.gallery3d.ui.GLRoot;
52 import com.android.gallery3d.ui.GLView;
53 import com.android.gallery3d.ui.RelativePosition;
54 import com.android.gallery3d.ui.SelectionManager;
55 import com.android.gallery3d.ui.SlotView;
56 import com.android.gallery3d.ui.SynchronizedHandler;
57 import com.android.gallery3d.util.Future;
58 import com.android.gallery3d.util.GalleryUtils;
59
60 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
61         SelectionManager.SelectionListener, MediaSet.SyncListener {
62     @SuppressWarnings("unused")
63     private static final String TAG = "AlbumPage";
64
65     private static final int MSG_PICK_PHOTO = 1;
66
67     public static final String KEY_MEDIA_PATH = "media-path";
68     public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
69     public static final String KEY_SET_CENTER = "set-center";
70     public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
71     public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
72
73     private static final int REQUEST_SLIDESHOW = 1;
74     private static final int REQUEST_PHOTO = 2;
75     private static final int REQUEST_DO_ANIMATION = 3;
76
77     private static final int BIT_LOADING_RELOAD = 1;
78     private static final int BIT_LOADING_SYNC = 2;
79
80     private static final float USER_DISTANCE_METER = 0.3f;
81
82     private boolean mIsActive = false;
83     private AlbumSlotRenderer mAlbumView;
84     private Path mMediaSetPath;
85     private String mParentMediaSetString;
86     private SlotView mSlotView;
87
88     private AlbumDataLoader mAlbumDataAdapter;
89
90     protected SelectionManager mSelectionManager;
91     private Vibrator mVibrator;
92
93     private boolean mGetContent;
94     private boolean mShowClusterMenu;
95
96     private ActionMode mActionMode;
97     private ActionModeHandler mActionModeHandler;
98     private int mFocusIndex = 0;
99     private DetailsHelper mDetailsHelper;
100     private MyDetailsSource mDetailsSource;
101     private MediaSet mMediaSet;
102     private boolean mShowDetails;
103     private float mUserDistance; // in pixel
104     private Handler mHandler;
105
106     private Future<Integer> mSyncTask = null;
107
108     private int mLoadingBits = 0;
109     private boolean mInitialSynced = false;
110     private RelativePosition mOpenCenter = new RelativePosition();
111
112     private final GLView mRootPane = new GLView() {
113         private final float mMatrix[] = new float[16];
114
115         @Override
116         protected void renderBackground(GLCanvas view) {
117             view.clearBuffer();
118         }
119
120         @Override
121         protected void onLayout(
122                 boolean changed, int left, int top, int right, int bottom) {
123
124             int slotViewTop = mActivity.getGalleryActionBar().getHeight();
125             int slotViewBottom = bottom - top;
126             int slotViewRight = right - left;
127
128             if (mShowDetails) {
129                 mDetailsHelper.layout(left, slotViewTop, right, bottom);
130             } else {
131                 mAlbumView.setHighlightItemPath(null);
132             }
133
134             // Set the mSlotView as a reference point to the open animation
135             mOpenCenter.setReferencePosition(0, slotViewTop);
136             mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
137             GalleryUtils.setViewPointMatrix(mMatrix,
138                     (right - left) / 2, (bottom - top) / 2, -mUserDistance);
139         }
140
141         @Override
142         protected void render(GLCanvas canvas) {
143             canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
144             canvas.multiplyMatrix(mMatrix, 0);
145             super.render(canvas);
146             canvas.restore();
147         }
148     };
149
150     @Override
151     protected void onBackPressed() {
152         if (mShowDetails) {
153             hideDetails();
154         } else if (mSelectionManager.inSelectionMode()) {
155             mSelectionManager.leaveSelectionMode();
156         } else {
157             // TODO: fix this regression
158             // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
159             super.onBackPressed();
160         }
161     }
162
163     private void onDown(int index) {
164         mAlbumView.setPressedIndex(index);
165     }
166
167     private void onUp(boolean followedByLongPress) {
168         if (followedByLongPress) {
169             // Avoid showing press-up animations for long-press.
170             mAlbumView.setPressedIndex(-1);
171         } else {
172             mAlbumView.setPressedUp();
173         }
174     }
175
176     private void onSingleTapUp(int slotIndex) {
177         if (!mIsActive) return;
178
179         if (mSelectionManager.inSelectionMode()) {
180             MediaItem item = mAlbumDataAdapter.get(slotIndex);
181             if (item == null) return; // Item not ready yet, ignore the click
182             mSelectionManager.toggle(item.getPath());
183             mSlotView.invalidate();
184         } else {
185             // Show pressed-up animation for the single-tap.
186             mAlbumView.setPressedIndex(slotIndex);
187             mAlbumView.setPressedUp();
188             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0),
189                     FadeTexture.DURATION);
190         }
191     }
192
193     private void pickPhoto(int slotIndex) {
194         if (!mIsActive) return;
195
196         MediaItem item = mAlbumDataAdapter.get(slotIndex);
197         if (item == null) return; // Item not ready yet, ignore the click
198         if (mGetContent) {
199             onGetContent(item);
200         } else {
201             // Get into the PhotoPage.
202             // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
203             Bundle data = new Bundle();
204             data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
205             data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
206                     getSlotRect(slotIndex));
207             data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
208                     mMediaSetPath.toString());
209             data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
210                     item.getPath().toString());
211             mActivity.getStateManager().startStateForResult(
212                     PhotoPage.class, REQUEST_PHOTO, data);
213         }
214     }
215
216     private Rect getSlotRect(int slotIndex) {
217         // Get slot rectangle relative to this root pane.
218         Rect offset = new Rect();
219         mRootPane.getBoundsOf(mSlotView, offset);
220         Rect r = mSlotView.getSlotRect(slotIndex);
221         r.offset(offset.left - mSlotView.getScrollX(),
222                 offset.top - mSlotView.getScrollY());
223         return r;
224     }
225
226     private void onGetContent(final MediaItem item) {
227         DataManager dm = mActivity.getDataManager();
228         Activity activity = (Activity) mActivity;
229         if (mData.getString(Gallery.EXTRA_CROP) != null) {
230             // TODO: Handle MtpImagew
231             Uri uri = dm.getContentUri(item.getPath());
232             Intent intent = new Intent(CropImage.ACTION_CROP, uri)
233                     .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
234                     .putExtras(getData());
235             if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
236                 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
237             }
238             activity.startActivity(intent);
239             activity.finish();
240         } else {
241             activity.setResult(Activity.RESULT_OK,
242                     new Intent(null, item.getContentUri()));
243             activity.finish();
244         }
245     }
246
247     public void onLongTap(int slotIndex) {
248         if (mGetContent) return;
249         MediaItem item = mAlbumDataAdapter.get(slotIndex);
250         if (item == null) return;
251         mSelectionManager.setAutoLeaveSelectionMode(true);
252         mSelectionManager.toggle(item.getPath());
253         mSlotView.invalidate();
254     }
255
256     @Override
257     public void doCluster(int clusterType) {
258         String basePath = mMediaSet.getPath().toString();
259         String newPath = FilterUtils.newClusterPath(basePath, clusterType);
260         Bundle data = new Bundle(getData());
261         data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
262         if (mShowClusterMenu) {
263             Context context = mActivity.getAndroidContext();
264             data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
265             data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
266                     GalleryActionBar.getClusterByTypeString(context, clusterType));
267         }
268
269         // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
270         mActivity.getStateManager().startStateForResult(
271                 AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
272     }
273
274     @Override
275     protected void onCreate(Bundle data, Bundle restoreState) {
276         mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
277         initializeViews();
278         initializeData(data);
279         mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
280         mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
281         mDetailsSource = new MyDetailsSource();
282         Context context = mActivity.getAndroidContext();
283         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
284
285         // Enable auto-select-all for mtp album
286         if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
287             mSelectionManager.selectAll();
288         }
289
290         // Don't show animation if it is restored
291         if (restoreState == null && data != null) {
292             int[] center = data.getIntArray(KEY_SET_CENTER);
293             if (center != null) {
294                 mOpenCenter.setAbsolutePosition(center[0], center[1]);
295                 mSlotView.startScatteringAnimation(mOpenCenter);
296             }
297         }
298
299         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
300             @Override
301             public void handleMessage(Message message) {
302                 switch (message.what) {
303                     case MSG_PICK_PHOTO: {
304                         pickPhoto(message.arg1);
305                         break;
306                     }
307                     default: throw new AssertionError(message.what);
308                 }
309             }
310         };
311     }
312
313     @Override
314     protected void onResume() {
315         super.onResume();
316         mIsActive = true;
317         setContentPane(mRootPane);
318
319         Path path = mMediaSet.getPath();
320         boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
321                 mParentMediaSetString != null;
322         mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
323
324         // Set the reload bit here to prevent it exit this page in clearLoadingBit().
325         setLoadingBit(BIT_LOADING_RELOAD);
326         mAlbumDataAdapter.resume();
327
328         mAlbumView.resume();
329         mActionModeHandler.resume();
330         if (!mInitialSynced) {
331             setLoadingBit(BIT_LOADING_SYNC);
332             mSyncTask = mMediaSet.requestSync(this);
333         }
334     }
335
336     @Override
337     protected void onPause() {
338         super.onPause();
339         mIsActive = false;
340         mAlbumDataAdapter.pause();
341         mAlbumView.pause();
342         DetailsHelper.pause();
343
344         if (mSyncTask != null) {
345             mSyncTask.cancel();
346             mSyncTask = null;
347             clearLoadingBit(BIT_LOADING_SYNC);
348         }
349         mActionModeHandler.pause();
350         GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
351     }
352
353     @Override
354     protected void onDestroy() {
355         super.onDestroy();
356         if (mAlbumDataAdapter != null) {
357             mAlbumDataAdapter.setLoadingListener(null);
358         }
359     }
360
361     private void initializeViews() {
362         mSelectionManager = new SelectionManager(mActivity, false);
363         mSelectionManager.setSelectionListener(this);
364         Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
365         mSlotView = new SlotView(mActivity, config.slotViewSpec);
366         mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager);
367         mSlotView.setSlotRenderer(mAlbumView);
368         mRootPane.addComponent(mSlotView);
369         mSlotView.setListener(new SlotView.SimpleListener() {
370             @Override
371             public void onDown(int index) {
372                 AlbumPage.this.onDown(index);
373             }
374
375             @Override
376             public void onUp(boolean followedByLongPress) {
377                 AlbumPage.this.onUp(followedByLongPress);
378             }
379
380             @Override
381             public void onSingleTapUp(int slotIndex) {
382                 AlbumPage.this.onSingleTapUp(slotIndex);
383             }
384
385             @Override
386             public void onLongTap(int slotIndex) {
387                 AlbumPage.this.onLongTap(slotIndex);
388             }
389         });
390         mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
391         mActionModeHandler.setActionModeListener(new ActionModeListener() {
392             public boolean onActionItemClicked(MenuItem item) {
393                 return onItemSelected(item);
394             }
395         });
396     }
397
398     private void initializeData(Bundle data) {
399         mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
400         mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
401         mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
402         if (mMediaSet == null) {
403             Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
404         }
405         mSelectionManager.setSourceMediaSet(mMediaSet);
406         mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
407         mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
408         mAlbumView.setModel(mAlbumDataAdapter);
409     }
410
411     private void showDetails() {
412         mShowDetails = true;
413         if (mDetailsHelper == null) {
414             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
415             mDetailsHelper.setCloseListener(new CloseListener() {
416                 public void onClose() {
417                     hideDetails();
418                 }
419             });
420         }
421         mDetailsHelper.show();
422     }
423
424     private void hideDetails() {
425         mShowDetails = false;
426         mDetailsHelper.hide();
427         mAlbumView.setHighlightItemPath(null);
428         mSlotView.invalidate();
429     }
430
431     @Override
432     protected boolean onCreateActionBar(Menu menu) {
433         Activity activity = (Activity) mActivity;
434         GalleryActionBar actionBar = mActivity.getGalleryActionBar();
435         MenuInflater inflater = activity.getMenuInflater();
436
437         if (mGetContent) {
438             inflater.inflate(R.menu.pickup, menu);
439             int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
440                     DataManager.INCLUDE_IMAGE);
441
442             actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
443         } else {
444             inflater.inflate(R.menu.album, menu);
445             actionBar.setTitle(mMediaSet.getName());
446             if (mMediaSet instanceof MtpDevice) {
447                 menu.findItem(R.id.action_slideshow).setVisible(false);
448             } else {
449                 menu.findItem(R.id.action_slideshow).setVisible(true);
450             }
451
452             MenuItem groupBy = menu.findItem(R.id.action_group_by);
453             FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
454
455             if (groupBy != null) {
456                 groupBy.setVisible(mShowClusterMenu);
457             }
458
459             actionBar.setTitle(mMediaSet.getName());
460         }
461         actionBar.setSubtitle(null);
462
463         return true;
464     }
465
466     @Override
467     protected boolean onItemSelected(MenuItem item) {
468         switch (item.getItemId()) {
469             case android.R.id.home: {
470                 if (mActivity.getStateManager().getStateCount() > 1) {
471                     onBackPressed();
472                 } else if (mParentMediaSetString != null) {
473                     Bundle data = new Bundle(getData());
474                     data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
475                     mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
476                 }
477                 return true;
478             }
479             case R.id.action_cancel:
480                 mActivity.getStateManager().finishState(this);
481                 return true;
482             case R.id.action_select:
483                 mSelectionManager.setAutoLeaveSelectionMode(false);
484                 mSelectionManager.enterSelectionMode();
485                 return true;
486             case R.id.action_group_by: {
487                 mActivity.getGalleryActionBar().showClusterDialog(this);
488                 return true;
489             }
490             case R.id.action_slideshow: {
491                 Bundle data = new Bundle();
492                 data.putString(SlideshowPage.KEY_SET_PATH,
493                         mMediaSetPath.toString());
494                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
495                 mActivity.getStateManager().startStateForResult(
496                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
497                 return true;
498             }
499             case R.id.action_details: {
500                 if (mShowDetails) {
501                     hideDetails();
502                 } else {
503                     showDetails();
504                 }
505                 return true;
506             }
507             default:
508                 return false;
509         }
510     }
511
512     @Override
513     protected void onStateResult(int request, int result, Intent data) {
514         switch (request) {
515             case REQUEST_SLIDESHOW: {
516                 // data could be null, if there is no images in the album
517                 if (data == null) return;
518                 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
519                 mSlotView.setCenterIndex(mFocusIndex);
520                 break;
521             }
522             case REQUEST_PHOTO: {
523                 if (data == null) return;
524                 mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
525                 mSlotView.setCenterIndex(mFocusIndex);
526                 mSlotView.startRestoringAnimation(mFocusIndex);
527                 break;
528             }
529             case REQUEST_DO_ANIMATION: {
530                 mSlotView.startRisingAnimation();
531                 break;
532             }
533         }
534     }
535
536     public void onSelectionModeChange(int mode) {
537         switch (mode) {
538             case SelectionManager.ENTER_SELECTION_MODE: {
539                 mActionMode = mActionModeHandler.startActionMode();
540                 mVibrator.vibrate(100);
541                 break;
542             }
543             case SelectionManager.LEAVE_SELECTION_MODE: {
544                 mActionMode.finish();
545                 mRootPane.invalidate();
546                 break;
547             }
548             case SelectionManager.SELECT_ALL_MODE: {
549                 mActionModeHandler.updateSupportedOperation();
550                 mRootPane.invalidate();
551                 break;
552             }
553         }
554     }
555
556     public void onSelectionChange(Path path, boolean selected) {
557         Utils.assertTrue(mActionMode != null);
558         int count = mSelectionManager.getSelectedCount();
559         String format = mActivity.getResources().getQuantityString(
560                 R.plurals.number_of_items_selected, count);
561         mActionModeHandler.setTitle(String.format(format, count));
562         mActionModeHandler.updateSupportedOperation(path, selected);
563     }
564
565     @Override
566     public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
567         Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
568                 + resultCode);
569         ((Activity) mActivity).runOnUiThread(new Runnable() {
570             @Override
571             public void run() {
572                 GLRoot root = mActivity.getGLRoot();
573                 root.lockRenderThread();
574                 try {
575                     if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
576                         mInitialSynced = true;
577                     }
578                     clearLoadingBit(BIT_LOADING_SYNC);
579                     if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
580                         Toast.makeText((Context) mActivity, R.string.sync_album_error,
581                                 Toast.LENGTH_LONG).show();
582                     }
583                 } finally {
584                     root.unlockRenderThread();
585                 }
586             }
587         });
588     }
589
590     private void setLoadingBit(int loadTaskBit) {
591         if (mLoadingBits == 0 && mIsActive) {
592             GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
593         }
594         mLoadingBits |= loadTaskBit;
595     }
596
597     private void clearLoadingBit(int loadTaskBit) {
598         mLoadingBits &= ~loadTaskBit;
599         if (mLoadingBits == 0 && mIsActive) {
600             GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
601
602             if (mAlbumDataAdapter.size() == 0) {
603                 Toast.makeText((Context) mActivity,
604                         R.string.empty_album, Toast.LENGTH_LONG).show();
605                 mActivity.getStateManager().finishState(AlbumPage.this);
606             }
607         }
608     }
609
610     private class MyLoadingListener implements LoadingListener {
611         @Override
612         public void onLoadingStarted() {
613             setLoadingBit(BIT_LOADING_RELOAD);
614         }
615
616         @Override
617         public void onLoadingFinished() {
618             clearLoadingBit(BIT_LOADING_RELOAD);
619         }
620     }
621
622     private class MyDetailsSource implements DetailsHelper.DetailsSource {
623         private int mIndex;
624
625         public int size() {
626             return mAlbumDataAdapter.size();
627         }
628
629         public int getIndex() {
630             return mIndex;
631         }
632
633         // If requested index is out of active window, suggest a valid index.
634         // If there is no valid index available, return -1.
635         public int findIndex(int indexHint) {
636             if (mAlbumDataAdapter.isActive(indexHint)) {
637                 mIndex = indexHint;
638             } else {
639                 mIndex = mAlbumDataAdapter.getActiveStart();
640                 if (!mAlbumDataAdapter.isActive(mIndex)) {
641                     return -1;
642                 }
643             }
644             return mIndex;
645         }
646
647         public MediaDetails getDetails() {
648             MediaObject item = mAlbumDataAdapter.get(mIndex);
649             if (item != null) {
650                 mAlbumView.setHighlightItemPath(item.getPath());
651                 return item.getDetails();
652             } else {
653                 return null;
654             }
655         }
656     }
657 }