1 package net.osdn.gokigen.gr2control.playback;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.HashMap;
9 import java.util.Locale;
11 import java.util.concurrent.ExecutorService;
12 import java.util.concurrent.Executors;
14 import android.app.Activity;
15 import android.app.AlertDialog;
16 import android.content.Context;
17 import android.content.SharedPreferences;
18 import android.graphics.Bitmap;
19 import android.preference.PreferenceManager;
20 import android.os.Bundle;
21 import android.util.Log;
22 import android.util.LruCache;
23 import android.view.LayoutInflater;
24 import android.view.Menu;
25 import android.view.MenuInflater;
26 import android.view.MenuItem;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.AbsListView;
30 import android.widget.AdapterView;
31 import android.widget.BaseAdapter;
32 import android.widget.GridView;
33 import android.widget.ImageView;
34 import android.widget.ProgressBar;
37 import net.osdn.gokigen.gr2control.R;
38 import net.osdn.gokigen.gr2control.camera.ICameraFileInfo;
39 import net.osdn.gokigen.gr2control.camera.ICameraRunMode;
40 import net.osdn.gokigen.gr2control.camera.ICameraRunModeCallback;
41 import net.osdn.gokigen.gr2control.camera.playback.ICameraContentListCallback;
42 import net.osdn.gokigen.gr2control.camera.playback.IDownloadThumbnailImageCallback;
43 import net.osdn.gokigen.gr2control.camera.playback.IPlaybackControl;
44 import net.osdn.gokigen.gr2control.playback.detail.ImageContentInfoEx;
45 import net.osdn.gokigen.gr2control.playback.detail.ImagePagerViewFragment;
46 import net.osdn.gokigen.gr2control.playback.detail.MyContentDownloader;
47 import net.osdn.gokigen.gr2control.preference.IPreferencePropertyAccessor;
49 import androidx.annotation.NonNull;
50 import androidx.appcompat.app.ActionBar;
51 import androidx.appcompat.app.AppCompatActivity;
52 import androidx.fragment.app.Fragment;
53 import androidx.fragment.app.FragmentActivity;
54 import androidx.fragment.app.FragmentTransaction;
56 public class ImageGridViewFragment extends Fragment implements ICameraRunModeCallback
58 private final String TAG = this.toString();
59 private final String MOVIE_SUFFIX = ".mov";
60 private final String JPEG_SUFFIX = ".jpg";
61 private final String DNG_RAW_SUFFIX = ".dng";
62 private final String OLYMPUS_RAW_SUFFIX = ".orf";
63 private final String PENTAX_RAW_PEF_SUFFIX = ".pef";
65 private MyContentDownloader contentDownloader;
66 private GridView gridView;
67 private boolean gridViewIsScrolling;
68 private IPlaybackControl playbackControl;
69 private ICameraRunMode runMode;
71 private List<ImageContentInfoEx> contentList;
72 private ExecutorService executor;
73 private LruCache<String, Bitmap> imageCache;
75 public static ImageGridViewFragment newInstance(@NonNull IPlaybackControl playbackControl, @NonNull ICameraRunMode runMode)
77 ImageGridViewFragment fragment = new ImageGridViewFragment();
78 fragment.setControllers(playbackControl, runMode);
82 private void setControllers(IPlaybackControl playbackControl, ICameraRunMode runMode)
84 this.playbackControl = playbackControl;
85 this.runMode = runMode;
86 Activity activity = getActivity();
89 this.contentDownloader = new MyContentDownloader(getActivity(), playbackControl);
93 this.contentDownloader = null;
98 public void onCreate(Bundle savedInstanceState)
100 super.onCreate(savedInstanceState);
101 Log.v(TAG, "ImageGridViewFragment::onCreate()");
103 executor = Executors.newFixedThreadPool(1);
104 imageCache = new LruCache<>(160);
105 setHasOptionsMenu(true);
109 public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
111 Log.v(TAG, "ImageGridViewFragment::onCreateView()");
112 View view = inflater.inflate(R.layout.fragment_image_grid_view, container, false);
114 gridView = view.findViewById(R.id.gridView1);
115 gridView.setAdapter(new GridViewAdapter(inflater));
116 GridViewOnItemClickListener listener = new GridViewOnItemClickListener();
117 gridView.setOnItemClickListener(listener);
118 gridView.setOnItemLongClickListener(listener);
119 gridView.setOnScrollListener(new GridViewOnScrollListener());
125 public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater)
127 inflater.inflate(R.menu.image_grid_view, menu);
128 String title = getString(R.string.app_name);
129 AppCompatActivity activity = (AppCompatActivity) getActivity();
130 if (activity != null)
132 ActionBar bar = activity.getSupportActionBar();
141 public boolean onOptionsItemSelected(MenuItem item)
143 int id = item.getItemId();
144 if (id == R.id.action_refresh)
149 if (id == R.id.action_batch_download_original_size_raw)
152 startDownloadBatch(false);
155 if (id == R.id.action_batch_download_640x480_raw)
158 startDownloadBatch(true);
161 if (id == R.id.action_select_all)
166 return (super.onOptionsItemSelected(item));
170 public void onResume()
173 Log.v(TAG, "onResume() Start");
174 AppCompatActivity activity = (AppCompatActivity)getActivity();
175 if (activity != null)
177 ActionBar bar = activity.getSupportActionBar();
181 boolean isShowActionBar = false;
182 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
183 if (preferences != null)
185 isShowActionBar = preferences.getBoolean(IPreferencePropertyAccessor.USE_PLAYBACK_MENU, true);
189 bar.show(); // ActionBarの表示を出す
193 bar.hide(); // ActionBarの表示を消す
206 Log.v(TAG, "onResume() End");
210 public void onPause()
212 Log.v(TAG, "onPause() Start");
213 if (!runMode.isRecordingMode())
215 // Threadで呼んではダメみたいだ...
216 //runMode.changeRunMode(true, this);
218 Log.v(TAG, "onPause() End");
221 postProcessChangeRunMode(true);
223 Log.v(TAG, "onPause() End");
226 private void postProcessChangeRunMode(boolean isRecording)
232 if (!executor.isShutdown())
234 executor.shutdownNow();
249 public void onCompleted(boolean isRecording)
251 postProcessChangeRunMode(isRecording);
255 public void onErrorOccurred(boolean isRecording)
257 postProcessChangeRunMode(isRecording);
263 Log.v(TAG, "onStop()");
267 private void refresh()
271 if (runMode.isRecordingMode())
273 runMode.changeRunMode(false, this);
282 Thread thread = new Thread(new Runnable() {
290 runOnUiThread(new Runnable() {
293 showHideProgressBar(true);
304 private void showHideProgressBar(final boolean isVisible)
306 Activity activity = getActivity();
307 if (activity != null)
309 ProgressBar bar = getActivity().findViewById(R.id.progress_bar);
312 bar.setVisibility((isVisible) ? View.VISIBLE : View.GONE);
318 private void refreshImpl()
321 Log.v(TAG, "refreshImpl() start");
323 playbackControl.downloadContentList(new ICameraContentListCallback() {
325 public void onCompleted(List<ICameraFileInfo> list) {
326 // Sort contents in chronological order (or alphabetical order).
327 Collections.sort(list, new Comparator<ICameraFileInfo>() {
329 public int compare(ICameraFileInfo lhs, ICameraFileInfo rhs)
331 long diff = rhs.getDatetime().getTime() - lhs.getDatetime().getTime();
334 diff = rhs.getFilename().compareTo(lhs.getFilename());
336 return (int)Math.min(Math.max(-1, diff), 1);
340 List<ImageContentInfoEx> contentItems = new ArrayList<>();
341 HashMap<String, ImageContentInfoEx> rawItems = new HashMap<>();
342 for (ICameraFileInfo item : list)
344 String path = item.getFilename().toLowerCase(Locale.getDefault());
345 if ((path.toLowerCase().endsWith(JPEG_SUFFIX))||(path.toLowerCase().endsWith(MOVIE_SUFFIX)))
347 contentItems.add(new ImageContentInfoEx(item, false, ""));
349 else if (path.toLowerCase().endsWith(DNG_RAW_SUFFIX))
351 //rawItems.put(path, new ImageContentInfoEx(item, true, DNG_RAW_SUFFIX));
352 contentItems.add(new ImageContentInfoEx(item, true, DNG_RAW_SUFFIX));
354 else if (path.toLowerCase().endsWith(OLYMPUS_RAW_SUFFIX))
356 rawItems.put(path, new ImageContentInfoEx(item, true, OLYMPUS_RAW_SUFFIX));
358 else if (path.toLowerCase().endsWith(PENTAX_RAW_PEF_SUFFIX))
360 //rawItems.put(path, new ImageContentInfoEx(item, true, PENTAX_RAW_PEF_SUFFIX));
361 contentItems.add(new ImageContentInfoEx(item, true, PENTAX_RAW_PEF_SUFFIX));
365 //List<ImageContentInfoEx> appendRawContents = new ArrayList<>();
366 for (ImageContentInfoEx item : contentItems)
368 String path = item.getFileInfo().getFilename().toLowerCase(Locale.getDefault());
369 if (path.toLowerCase().endsWith(JPEG_SUFFIX))
372 String target1 = path.replace(JPEG_SUFFIX, DNG_RAW_SUFFIX);
373 ImageContentInfoEx raw1 = rawItems.get(target1);
376 // JPEGファイルとRAWファイルがあるので、それをマークする
377 item.setHasRaw(true, DNG_RAW_SUFFIX);
378 Log.v(TAG, "DETECT RAW FILE: " + target1);
382 // RAWだけあった場合、一覧に追加する
383 appendRawContents.add(rawItems.get(path));
386 String target2 = path.replace(JPEG_SUFFIX, OLYMPUS_RAW_SUFFIX);
387 ImageContentInfoEx raw2 = rawItems.get(target2);
390 // RAW は、JPEGファイルがあった場合にのみリストする
391 item.setHasRaw(true, OLYMPUS_RAW_SUFFIX);
392 Log.v(TAG, "DETECT RAW FILE: " + target2);
395 String target3 = path.replace(JPEG_SUFFIX, PENTAX_RAW_PEF_SUFFIX);
396 ImageContentInfoEx raw3 = rawItems.get(target3);
399 // RAW は、JPEGファイルがあった場合にのみリストする
400 item.setHasRaw(true, PENTAX_RAW_PEF_SUFFIX);
401 Log.v(TAG, "DETECT RAW FILE: " + target3);
405 // RAWだけあった場合、一覧に追加する
406 appendRawContents.add(rawItems.get(path));
411 //contentItems.addAll(appendRawContents);
412 contentList = contentItems;
414 runOnUiThread(new Runnable() {
417 showHideProgressBar(false);
418 gridView.invalidateViews();
424 public void onErrorOccurred(Exception e) {
425 final String message = e.getMessage();
426 runOnUiThread(new Runnable() {
429 showHideProgressBar(false);
430 presentMessage("Load failed", message);
435 Log.v(TAG, "refreshImpl() end");
442 private void selectUnselectAll()
444 if ((contentList == null)||(contentList.size() == 0))
451 for (ImageContentInfoEx content : contentList)
453 if (content.isSelected())
459 // 全部選択されているときは全選択解除・そうでない時は全選択
460 boolean setSelected = (nofSelected != contentList.size());
461 for (ImageContentInfoEx content : contentList)
463 content.setSelected(setSelected);
470 private void redrawGridView()
473 Activity activity = getActivity();
474 if (activity != null)
476 getActivity().runOnUiThread(new Runnable()
481 if (gridView != null)
483 gridView.invalidateViews();
493 * @param isSmall 小さいサイズ(JPEG)
495 private void startDownloadBatch(final boolean isSmall)
499 // 念のため、contentDownloader がなければ作る
500 if (contentDownloader == null)
502 Activity activity = getActivity();
503 if (activity == null)
505 // activityが取れない時には終わる。
508 this.contentDownloader = new MyContentDownloader(getActivity(), playbackControl);
510 Thread thread = new Thread(new Runnable()
519 for (ImageContentInfoEx content : contentList)
521 if (content.isSelected())
528 // 画像が選択されていなかった...終了する
532 for (ImageContentInfoEx content : contentList)
534 if (content.isSelected())
536 contentDownloader.startDownload(content.getFileInfo(), " (" + count + "/" + totalSize + ") ", null, isSmall);
540 content.setSelected(false);
542 // ここでダウンロードが終わるまで、すこし待つ
553 } while (contentDownloader.isDownloading());
575 private static class GridCellViewHolder
579 ImageView selectView;
582 private class GridViewAdapter extends BaseAdapter
584 private LayoutInflater inflater;
586 GridViewAdapter(LayoutInflater inflater)
588 this.inflater = inflater;
591 private List<?> getItemList()
593 return (contentList);
597 public int getCount()
599 if (getItemList() == null)
603 return getItemList().size();
607 public Object getItem(int position)
609 if (getItemList() == null)
613 return (getItemList().get(position));
617 public long getItemId(int position)
623 public View getView(int position, View convertView, ViewGroup parent)
625 GridCellViewHolder viewHolder;
626 if (convertView == null)
628 convertView = inflater.inflate(R.layout.view_grid_cell, parent, false);
630 viewHolder = new GridCellViewHolder();
631 viewHolder.imageView = convertView.findViewById(R.id.imageViewY);
632 viewHolder.iconView = convertView.findViewById(R.id.imageViewZ);
633 viewHolder.selectView = convertView.findViewById(R.id.imageViewX);
635 convertView.setTag(viewHolder);
639 viewHolder = (GridCellViewHolder)convertView.getTag();
642 ImageContentInfoEx infoEx = (ImageContentInfoEx) getItem(position);
643 ICameraFileInfo item = (infoEx != null) ? infoEx.getFileInfo() : null;
646 viewHolder.imageView.setImageResource(R.drawable.ic_satellite_grey_24dp);
647 viewHolder.iconView.setImageDrawable(null);
648 viewHolder.selectView.setImageDrawable(null);
651 String path = new File(item.getDirectoryPath(), item.getFilename()).getPath();
652 Bitmap thumbnail = imageCache.get(path);
653 if (thumbnail == null)
655 viewHolder.imageView.setImageResource(R.drawable.ic_satellite_grey_24dp);
656 viewHolder.iconView.setImageDrawable(null);
657 if (!gridViewIsScrolling)
659 if (executor.isShutdown())
661 executor = Executors.newFixedThreadPool(1);
663 executor.execute(new ThumbnailLoader(viewHolder, path, infoEx.hasRaw()));
668 viewHolder.imageView.setImageBitmap(thumbnail);
669 if (path.toLowerCase().endsWith(MOVIE_SUFFIX))
671 viewHolder.iconView.setImageResource(R.drawable.ic_videocam_black_24dp);
673 else if (infoEx.hasRaw())
675 viewHolder.iconView.setImageResource(R.drawable.ic_raw_black_1x);
679 viewHolder.iconView.setImageDrawable(null);
682 if (infoEx.isSelected())
684 viewHolder.selectView.setImageResource(R.drawable.ic_check_green_24dp);
688 viewHolder.selectView.setImageDrawable(null);
694 private class GridViewOnItemClickListener implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener
697 public void onItemClick(AdapterView<?> parent, View view, int position, long id)
699 ImagePagerViewFragment fragment = ImagePagerViewFragment.newInstance(playbackControl, runMode, contentList, position);
700 FragmentActivity activity = getActivity();
701 if (activity != null)
703 FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
704 transaction.replace(getId(), fragment);
705 transaction.addToBackStack(null);
706 transaction.commit();
711 public boolean onItemLongClick(final AdapterView<?> parent, View view, int position, long id)
715 if (contentList == null)
719 ImageContentInfoEx infoEx = contentList.get(position);
722 boolean isChecked = infoEx.isSelected();
723 infoEx.setSelected(!isChecked);
726 runOnUiThread(new Runnable()
733 GridViewAdapter adapter = (GridViewAdapter) parent.getAdapter();
734 adapter.notifyDataSetChanged();
752 private class GridViewOnScrollListener implements AbsListView.OnScrollListener
755 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
761 public void onScrollStateChanged(AbsListView view, int scrollState)
763 if (scrollState == SCROLL_STATE_IDLE)
765 gridViewIsScrolling = false;
766 gridView.invalidateViews();
768 else if ((scrollState == SCROLL_STATE_FLING) || (scrollState == SCROLL_STATE_TOUCH_SCROLL))
770 gridViewIsScrolling = true;
771 if (!executor.isShutdown())
773 executor.shutdownNow();
779 private class ThumbnailLoader implements Runnable
781 private GridCellViewHolder viewHolder;
783 private final boolean hasRaw;
785 ThumbnailLoader(GridCellViewHolder viewHolder, String path, boolean hasRaw)
787 this.viewHolder = viewHolder;
789 this.hasRaw = hasRaw;
796 boolean isDownloading = true;
798 final Box box = new Box();
800 playbackControl.downloadContentThumbnail(null, path, new IDownloadThumbnailImageCallback()
803 public void onCompleted(final Bitmap thumbnail, Map<String, Object> metadata)
805 if (thumbnail != null)
808 Log.v(TAG, "Thumbnail PATH : " + path + " size : " + thumbnail.getByteCount());
809 imageCache.put(path, thumbnail);
810 runOnUiThread(new Runnable() {
813 viewHolder.imageView.setImageBitmap(thumbnail);
814 if (path.toLowerCase().endsWith(MOVIE_SUFFIX)) {
815 viewHolder.iconView.setImageResource(R.drawable.ic_videocam_black_24dp);
817 viewHolder.iconView.setImageResource(R.drawable.ic_raw_black_1x);
819 viewHolder.iconView.setImageDrawable(null);
823 } catch (Exception e) {
827 box.isDownloading = false;
831 public void onErrorOccurred(Exception e)
833 box.isDownloading = false;
837 // Waits to realize the serial download.
838 while (box.isDownloading) {
845 // -------------------------------------------------------------------------
847 // -------------------------------------------------------------------------
849 private void presentMessage(String title, String message)
851 Context context = getActivity();
857 AlertDialog.Builder builder = new AlertDialog.Builder(context);
858 builder.setTitle(title).setMessage(message);
862 private void runOnUiThread(Runnable action)
864 Activity activity = getActivity();
865 if (activity == null)
869 activity.runOnUiThread(action);
873 private Bitmap createRotatedBitmap(byte[] data, Map<String, Object> metadata)
875 Bitmap bitmap = null;
878 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
886 Log.v(TAG, "createRotatedBitmap() : bitmap is null : " + data.length);
890 int degrees = getRotationDegrees(data, metadata);
893 Matrix m = new Matrix();
894 m.postRotate(degrees);
897 bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
907 private int getRotationDegrees(byte[] data, Map<String, Object> metadata)
910 int orientation = ExifInterface.ORIENTATION_UNDEFINED;
912 if (metadata != null && metadata.containsKey("Orientation")) {
913 orientation = Integer.parseInt((String)metadata.get("Orientation"));
915 // Gets image orientation to display a picture.
917 File tempFile = File.createTempFile("temp", null);
919 FileOutputStream outStream = new FileOutputStream(tempFile.getAbsolutePath());
920 outStream.write(data);
924 ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
925 orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
927 if (!tempFile.delete())
929 Log.v(TAG, "File delete fail...");
932 catch (IOException e)
940 case ExifInterface.ORIENTATION_NORMAL:
943 case ExifInterface.ORIENTATION_ROTATE_90:
946 case ExifInterface.ORIENTATION_ROTATE_180:
949 case ExifInterface.ORIENTATION_ROTATE_270: