Java tutorial
package com.aimfire.gallery; /* * Copyright (c) 2016 Aimfire Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import com.aimfire.camarada.BuildConfig; import com.aimfire.camarada.R; import com.aimfire.constants.ActivityCode; import com.aimfire.main.MainConsts; import com.aimfire.drive.DownloadFileFragment; import com.aimfire.drive.UploadFileActivity; import com.aimfire.drive.DownloadFileFragment.DownloadStatusListener; import com.aimfire.drive.service.FileDownloaderService; import com.aimfire.gallery.cardboard.MovieActivity; import com.aimfire.gallery.cardboard.PhotoActivity; import com.aimfire.gallery.service.MovieProcessor; import com.aimfire.main.MainActivity; import com.aimfire.utilities.FileUtils; import com.aimfire.utilities.ZipUtil; import com.google.firebase.analytics.FirebaseAnalytics; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.LayoutParams; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewConfiguration; import android.view.Window; import android.view.WindowManager; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class GalleryActivity extends AppCompatActivity implements DownloadStatusListener { private static final String TAG = "GalleryActivity"; private static final boolean VERBOSE = true; private static final String KEY_PAGER_POSITION = "KPP"; private static final String KEY_PAGER_MY_MEDIA = "KPMM"; /* * this will control memory usage by ViewPager. smaller number will * mean less memory usage. however it might impact UI performance */ private static final int MAX_PAGE = 3; /** * Whether or not the action bar should be auto-hidden after * {@link #AUTO_HIDE_DELAY_SHORT_MILLIS} milliseconds. */ private static final boolean AUTO_HIDE = true; /** * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after * user interaction before hiding the system UI. */ private static final int AUTO_HIDE_DELAY_SHORT_MILLIS = 5000; private static final int AUTO_HIDE_DELAY_LONG_MILLIS = 15000; /* * firebase analytics */ private FirebaseAnalytics mFirebaseAnalytics; /* * display modes */ private DisplayMode mDisplayMode; private boolean mDisplaySwap; private boolean mDisplayColor; /* * view pager for full image display */ private ViewPager mViewPager; private ArrayList<String> mMediaList; private ImageView mNoMedia; private String mMediaPath; private String mMediaName; private String mPreviewPath; private String mPreviewName; /* * whether we see our media or shared media */ private boolean mIsMyMedia; /* * cardboard button */ private FloatingActionButton mCardboardButton; Handler mHideHandler = new Handler(); Runnable mHideRunnable = new Runnable() { @Override public void run() { getSupportActionBar().hide(); } }; /** * BroadcastReceiver for download completion and status messages. */ private BroadcastReceiver mDownloadMsgReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (BuildConfig.DEBUG) Log.d(TAG, "mDownloadMsgReceiver"); int what = intent.getIntExtra(MainConsts.EXTRA_WHAT, -1); String path = intent.getStringExtra(MainConsts.EXTRA_PATH); boolean isSuccess = intent.getBooleanExtra(MainConsts.EXTRA_STATUS, false); int percentage = intent.getIntExtra(MainConsts.EXTRA_MSG, -1); switch (what) { case MainConsts.MSG_FILE_DOWNLOADER_COMPLETION: if (!mIsMyMedia) { if (!isSuccess) { /* * we either failed to download a file or the downloaded file * is empty (which means upload on the other end is not done) */ updateViewPager(null, mViewPager.getCurrentItem(), -1); } else { /* * we come here if we successfully downloaded a file */ if (MediaScanner.isPreview(path)) { intent.putExtra(MainConsts.EXTRA_PATH, MediaScanner.getSharedMediaPathFromPreviewPath(path)); } parseIntent(intent); } } break; case MainConsts.MSG_FILE_DOWNLOADER_PROGRESS: if (!mIsMyMedia) { updateViewPager(path, -1, percentage); } break; case MainConsts.MSG_FILE_DOWNLOADER_SAMPLES_START: if (!mIsMyMedia) { updateViewPager(null, mViewPager.getCurrentItem(), -1); } break; default: break; } } }; /** * BroadcastReceiver for incoming media processor messages. */ private BroadcastReceiver mPhotoProcessorMsgReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int messageCode = intent.getIntExtra(MainConsts.EXTRA_WHAT, -1); switch (messageCode) { case MainConsts.MSG_PHOTO_PROCESSOR_RESULT: /* * ignore if we are showing shared media files */ if (mIsMyMedia) { parseIntent(intent); } break; case MainConsts.MSG_PHOTO_PROCESSOR_ERROR: String filePath = intent.getStringExtra(MainConsts.EXTRA_PATH); if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "onReceive: " + filePath + " auto alignment error"); if (mIsMyMedia) { /* * if we happen to be on the page waiting for this file, need to update */ updateViewPager(null, mViewPager.getCurrentItem(), -1); } break; default: break; } } }; /** * BroadcastReceiver for incoming media processor messages. */ private BroadcastReceiver mMovieProcessorMsgReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int messageCode = intent.getIntExtra(MainConsts.EXTRA_WHAT, -1); switch (messageCode) { case MainConsts.MSG_MOVIE_PROCESSOR_RESULT: /* * ignore if we are showing shared media files */ if (mIsMyMedia) { parseIntent(intent); } break; case MainConsts.MSG_MOVIE_PROCESSOR_ERROR: String filePath = intent.getStringExtra(MainConsts.EXTRA_PATH); if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "onReceive: " + filePath + " auto alignment error"); if (mIsMyMedia) { /* * if we happen to be on the page waiting for this file, need to update */ updateViewPager(null, mViewPager.getCurrentItem(), -1); } break; default: break; } } }; /** * Schedules a call to hide() in [delay] milliseconds, canceling any * previously scheduled calls. */ private void delayedHide(int delayMillis) { mHideHandler.removeCallbacks(mHideRunnable); mHideHandler.postDelayed(mHideRunnable, delayMillis); } private OnTouchListener otl = new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { getSupportActionBar().show(); if (AUTO_HIDE) { // Schedule a hide(). delayedHide(AUTO_HIDE_DELAY_SHORT_MILLIS); } return false; } }; OnPageChangeListener opcl = new OnPageChangeListener() { @Override public void onPageScrollStateChanged(int state) { //Called when the scroll state changes. } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { //This method will be invoked when the current page is scrolled, //either as part of a programmatically initiated smooth scroll //or a user initiated touch scroll. } @Override public void onPageSelected(int i) { shareUpdate(i); } }; private void loadDisplayPrefs() { SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE); if (settings != null) { mDisplaySwap = settings.getBoolean(MainConsts.DISPLAY_SWAP_PREFS_KEY, false); mDisplayColor = settings.getBoolean(MainConsts.DISPLAY_COLOR_PREFS_KEY, true); mDisplayMode = DisplayMode.values()[settings.getInt(MainConsts.DISPLAY_MODE_PREFS_KEY, DisplayMode.Anaglyph.getValue())]; } } private void updateDisplayPrefs() { SharedPreferences settings = getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE); if (settings != null) { SharedPreferences.Editor editor = settings.edit(); editor.putBoolean(MainConsts.DISPLAY_SWAP_PREFS_KEY, mDisplaySwap); editor.putBoolean(MainConsts.DISPLAY_COLOR_PREFS_KEY, mDisplayColor); editor.putInt(MainConsts.DISPLAY_MODE_PREFS_KEY, mDisplayMode.getValue()); editor.commit(); } } /** * onClick handler for "cardboard" button. */ OnClickListener oclCardboard = new OnClickListener() { @Override public void onClick(View view) { mDisplayMode = DisplayMode.Cardboard; switchDisplayMode(); } }; @Override public void onCreate(Bundle savedInstanceState) { supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); super.onCreate(savedInstanceState); setContentView(R.layout.activity_gallery); getSupportActionBar().setDisplayHomeAsUpEnabled(true); /* * load display mode, swap and color mode that were last used */ loadDisplayPrefs(); /* * show overflow button even if we have a physical menu button */ forceOverflowButton(); /* * Obtain the FirebaseAnalytics instance. */ mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); /* * initialize the view pager with current media we have */ initViewPager(); mNoMedia = (ImageView) findViewById(R.id.no_media_bg); mNoMedia.setOnTouchListener(otl); mCardboardButton = (FloatingActionButton) findViewById(R.id.cardboardFAB); mCardboardButton.setOnClickListener(oclCardboard); if (savedInstanceState != null) { mIsMyMedia = savedInstanceState.getBoolean(KEY_PAGER_MY_MEDIA); updateViewPager(null, savedInstanceState.getInt(KEY_PAGER_POSITION), -1); } else { /* * parse the intent */ Intent intent = getIntent(); parseIntent(intent); } showHideView(); /* * initial command to hide action bar */ if (AUTO_HIDE) { delayedHide(AUTO_HIDE_DELAY_SHORT_MILLIS); } } @Override protected void onNewIntent(Intent intent) { if (BuildConfig.DEBUG) Log.d(TAG, "onNewIntent"); parseIntent(intent); } /** * Override Activity lifecycle method. */ @Override protected void onResume() { super.onResume(); if (mDisplayMode == DisplayMode.Cardboard) { /* * reload display preferences (that's different from Cardboard) */ loadDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); } // /* // * upon resuming, the page that was being displayed may have // * been deleted, so need to update the view pager // */ // updateViewPager(null, mViewPager.getCurrentItem(), -1); /* * register for intents sent by the media processor service */ LocalBroadcastManager.getInstance(this).registerReceiver(mPhotoProcessorMsgReceiver, new IntentFilter(MainConsts.PHOTO_PROCESSOR_MESSAGE)); LocalBroadcastManager.getInstance(this).registerReceiver(mMovieProcessorMsgReceiver, new IntentFilter(MainConsts.MOVIE_PROCESSOR_MESSAGE)); /* * register for intents sent by the download completion service */ LocalBroadcastManager.getInstance(this).registerReceiver(mDownloadMsgReceiver, new IntentFilter(MainConsts.FILE_DOWNLOADER_MESSAGE)); } /** * Override Activity lifecycle method. */ @Override protected void onPause() { super.onPause(); if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "onPause: unregister local broadcast receiver"); /* * de-register for intents sent by the download completion service */ LocalBroadcastManager.getInstance(this).unregisterReceiver(mDownloadMsgReceiver); /* * de-register for intents sent by the media processor service */ LocalBroadcastManager.getInstance(this).unregisterReceiver(mPhotoProcessorMsgReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mMovieProcessorMsgReceiver); } /** * Override Activity lifecycle method. */ @Override protected void onDestroy() { if (mViewPager != null) { mViewPager.setAdapter(null); } super.onDestroy(); } // @Override // public void onBackPressed() // { // finish(); // } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); // if (hasFocus) // { // forceFullScreen(); // } } /** * this activity (GalleryActivity) can receive intent from: * 1. ThumbsFragment - when a thumbnail is selected * 2. PhotoProcessor - when a photo is finished processing * 3. MovieProcessor - when a movie is finished processing * 4. user click a .cvr file from file browser * 5. user click link (of amifire-vr scheme) in browser * 6. user click link (with our domain name) in mail or other programs (other than browser) * * in the first three cases, the intent we get will have extras EXTRA_PATH and EXTRA_MSG. * in the fourth case, we will have intent with getData with path to the .cvr file (or occasionally jpg file). * in the last two cases, we will have a link that we need to parse. */ private void parseIntent(Intent intent) { /* * set mIsMyMedia according to intent. we may or may not have EXTRA_MSG * passed in. in case we don't have it passed in we will set it to false * here by default. it may get overriden after we parsed the intent. */ mIsMyMedia = intent.getBooleanExtra(MainConsts.EXTRA_MSG, false); String filePath = null; Uri uri = intent.getData(); if (uri != null) { if (uri.getScheme().equalsIgnoreCase("file")) { filePath = handleLocalOpen(uri); } else if (uri.getScheme().equalsIgnoreCase("https") || uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("aimfire-vr")) { filePath = handleDownload(uri); } } else { filePath = intent.getStringExtra(MainConsts.EXTRA_PATH); } if (filePath == null) { if (BuildConfig.DEBUG) Log.e(TAG, "parseIntent: filePath is null"); return; } /* * update view pager - if file is already in the view pager, this will * updates its view only. if it is not, this will refresh the entire * view pager to add it. */ updateViewPager(filePath, -1, -1); } /** * handle opening of a cvr file already on the phone. if it is not in our directory * structure, we copy it there first. * * @param uri * @return path to the cvr file in our directory structure */ private String handleLocalOpen(Uri uri) { String path = null; /* * we are opened from file broswer */ File f = new File(uri.getPath()); if (f != null) { path = f.getAbsolutePath(); } /* * we support file open of cvr file only */ if ((path == null) || !MediaScanner.isMovie(path)) { Toast.makeText(this, R.string.error_incorrect_link, Toast.LENGTH_LONG).show(); return null; } if (path.contains(MainConsts.MEDIA_3D_SAVE_PATH)) { mIsMyMedia = true; } else if (path.contains(MainConsts.MEDIA_3D_SHARED_PATH)) { mIsMyMedia = false; } else { mIsMyMedia = false; /* * in case we got here directly without going thru MainActivity * first, it's possible we don't have storage initialized yet. */ if (FileUtils.initStorage()) { /* * we are launched from external apps by file type. move the file * to our root dir. if rename fails, we will have to do the actual * copy (rather than rename, as we may be copying the file from * internal to external storage; or we don't have write permission * on the source directory) */ String newFilePath = MainConsts.MEDIA_3D_SHARED_PATH + (new File(path)).getName(); boolean success = false; try { File from = (new File(path)); File to = (new File(newFilePath)); success = from.renameTo(to); } catch (Exception e) { e.printStackTrace(); } if (!success) { FileUtils.copyFile(path, newFilePath); } path = newFilePath; /* * make MediaScanner aware of the new file */ MediaScanner.addItemMediaList(path); } else { Toast.makeText(this, R.string.error_accessing_storage, Toast.LENGTH_LONG).show(); return null; } } /* * import/convert the file if it has the old format (.png preview frame) */ if (MediaScanner.is3dMovie(path)) { convertCvr(path); } return path; } private void convertCvr(String path) { boolean isOldCvr = false; ArrayList<String> filenames = new ArrayList<String>(); try { filenames = ZipUtil.getZipEntries(path); for (String i : filenames) { if (i.endsWith("png")) { isOldCvr = true; break; } } } catch (IOException e) { if (BuildConfig.DEBUG) if (VERBOSE) Log.e(TAG, "convertCvr: expection " + e.getMessage()); return; } if (!isOldCvr) { return; } DecodeCvrTask cvrTask = new DecodeCvrTask(path); cvrTask.execute(); try { cvrTask.get(); } catch (CancellationException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } String leftFilename = null; String rightFilename = null; for (String i : filenames) { if (i.contains("left") && i.endsWith("mp4")) { leftFilename = i.replace("cache/", ""); } else if (i.contains("right") && i.endsWith("mp4")) { rightFilename = i.replace("cache/", ""); ; } } int timeOffsetMs = MediaScanner.getMpgTimeOffsetMs(leftFilename, rightFilename); String leftFilepath = MainConsts.MEDIA_3D_RAW_PATH + leftFilename; String rightFilepath = MainConsts.MEDIA_3D_RAW_PATH + rightFilename; Intent serviceIntent = new Intent(this, MovieProcessor.class); serviceIntent.putExtra("lname", leftFilepath); serviceIntent.putExtra("rname", rightFilepath); serviceIntent.putExtra(MainConsts.EXTRA_SCALE, 1.0f); serviceIntent.putExtra(MainConsts.EXTRA_FACING, 0/*assuming back facing*/); serviceIntent.putExtra(MainConsts.EXTRA_OFFSET, timeOffsetMs); startService(serviceIntent); } /** * parse the link that led us here. check if the file already exists. if not, start * DownloadFileFragment. * * @param uri * @return path to the file download location. in the case of a chained download, we * will always return the path to the cvr file (rather than the preview file) */ private String handleDownload(Uri uri) { if (!isDeviceOnline()) { Toast.makeText(this, R.string.error_no_network, Toast.LENGTH_LONG).show(); return null; } String mePath = null; String sharePath = null; String resId = uri.getQueryParameter("id"); String origName = uri.getQueryParameter("name"); if ((resId == null) || (origName == null)) { Toast.makeText(this, R.string.error_incorrect_link, Toast.LENGTH_LONG).show(); return null; } saveDriveFileRecord(origName, resId); /* * we could have the size if this is the cvr download (chained-mode) */ int size = -1; String sizeStr = uri.getQueryParameter("size"); if (sizeStr != null) { try { size = Integer.parseInt(sizeStr); } catch (Exception e) { Toast.makeText(this, R.string.error_incorrect_link, Toast.LENGTH_LONG).show(); return null; } } /* * we check if the file already exists. file name may be: * * SBS...jpg - this is a 3D SBS image, non-chained mode * MPG...jpeg - this is a preview frame of movie, chained mode * * if a jpg or movie file exists in my media or shared media path, then we * either have successfully downloaded the photo or preview or we are * currently downloading it - remember we delete the placeholder jpg or * cvr file in case of failure. * * the link may specify a movie (cvr) file - we have the legacy code here * for it, but we currently don't use it. in that case the file name is: * * MPG...cvr - this is a 3D cvr movie, non-chained mode */ mePath = MediaScanner.getMyMediaPathFromOrigName(origName); sharePath = MediaScanner.getSharedMediaPathFromOrigName(origName); if (new File(mePath).exists()) { /* * this only happens if we click on the link that we sent to ourselves */ if (BuildConfig.DEBUG) Log.d(TAG, "handleDownload: file already exists in My Media!"); mIsMyMedia = true; return mePath; } else if (new File(sharePath).exists()) { /* * this happens if we have downloaded this file before */ if (BuildConfig.DEBUG) Log.d(TAG, "handleDownload: file already exists in Shared Media!"); mIsMyMedia = false; return sharePath; } /* * the file doesn't exist and we need to download it. * * create an empty, placeholder file, so GalleryActivity/ThumbsFragment knows * its existence and show a progress bar while this file is downloaded */ try { MediaScanner.addItemMediaList(sharePath); (new File(sharePath)).createNewFile(); } catch (IOException e) { Toast.makeText(this, R.string.error_accessing_storage, Toast.LENGTH_LONG).show(); return null; } if (MediaScanner.isMovie(origName)) { /* * legacy code - download cvr from a link. */ addDownloadFileFragment(resId, origName, size); } else { Intent serviceIntent = new Intent(this, FileDownloaderService.class); if (MediaScanner.isPreview(origName)) { serviceIntent.putExtra(MainConsts.EXTRA_PATH, MediaScanner.getPreviewPathFromOrigName(origName)); } else { serviceIntent.putExtra(MainConsts.EXTRA_PATH, sharePath); } startService(serviceIntent); } return sharePath; } private void forceFullScreen() { if (Build.VERSION.SDK_INT < 16) { /* * if the Android version is lower than Jellybean, use this call to hide * the status bar. */ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } else { View decorView = getWindow().getDecorView(); /* * hide the status bar. */ int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN; decorView.setSystemUiVisibility(uiOptions); } } /** * c.f. "http://stackoverflow.com/questions/9286822/how-to-force-use-of-overflow- * menu-on-devices-with-menu-button" */ private void forceOverflowButton() { try { ViewConfiguration config = ViewConfiguration.get(this); Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); if (menuKeyField != null) { menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); } } catch (Exception ex) { // Ignore } } /** * Override Activity lifecycle method. */ @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_PAGER_POSITION, mViewPager.getCurrentItem()); outState.putBoolean(KEY_PAGER_MY_MEDIA, mIsMyMedia); super.onSaveInstanceState(outState); } /** * Override Activity lifecycle method. */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_gallery, menu); if (mViewPager != null) { shareUpdate(mViewPager.getCurrentItem()); } /* * To show icon (instead of only text) in action bar overflow */ if (menu.getClass().getSimpleName().equals("MenuBuilder")) { try { Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); m.setAccessible(true); m.invoke(menu, true); } catch (NoSuchMethodException e) { if (BuildConfig.DEBUG) if (VERBOSE) Log.e(TAG, "onCreateOptionsMenu", e); } catch (Exception e) { throw new RuntimeException(e); } } return super.onCreateOptionsMenu(menu); } /** * Override Activity lifecycle method. */ @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem sbs = menu.findItem(R.id.action_sbs); MenuItem tv = menu.findItem(R.id.action_3dtv); MenuItem ag = menu.findItem(R.id.action_anaglyph); MenuItem sw = menu.findItem(R.id.action_swap); MenuItem gs = menu.findItem(R.id.action_grayscale); switch (mDisplayMode) { case SbsFull: sbs.setChecked(true); sw.setEnabled(true); if (mDisplaySwap) { sbs.setIcon(R.drawable.ic_crossed_eye); sbs.setTitle(R.string.action_sbs_cross); } else { sbs.setIcon(R.drawable.ic_parallel_eye_white); sbs.setTitle(R.string.action_sbs_parallel); } break; case SbsHalf: tv.setChecked(true); sw.setEnabled(false); break; case Anaglyph: ag.setChecked(true); sw.setEnabled(true); if (mDisplaySwap) { ag.setIcon(R.drawable.ic_cyan_red); ag.setTitle(R.string.action_anaglyph_cyanred); } else { ag.setIcon(R.drawable.ic_red_cyan); ag.setTitle(R.string.action_anaglyph_redcyan); } break; default: break; } if (mDisplaySwap) { sw.setChecked(true); } else { sw.setChecked(false); } if (mDisplayColor) { gs.setChecked(false); } else { gs.setChecked(true); } return super.onPrepareOptionsMenu(menu); } /** * Override Activity lifecycle method. */ @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent; /* * if user interacted with action bar, delay any scheduled hide() * operations to prevent the jarring behavior of controls going away * while interacting with the UI. */ if (AUTO_HIDE) { delayedHide(AUTO_HIDE_DELAY_SHORT_MILLIS); } switch (item.getItemId()) { case android.R.id.home: intent = new Intent(GalleryActivity.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(MainConsts.EXTRA_MSG, mIsMyMedia); startActivity(intent); break; case R.id.action_share: createDriveLink(); break; case R.id.action_thumb_view: switchThumbView(); break; case R.id.action_delete: deleteFile(); break; case R.id.action_anaglyph: if (!item.isChecked()) { mDisplayMode = DisplayMode.Anaglyph; if (item.getTitle().equals(getString(R.string.action_anaglyph_cyanred))) { mDisplaySwap = true; } else { mDisplaySwap = false; } item.setChecked(true); updateDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); } break; case R.id.action_sbs: if (!item.isChecked()) { mDisplayMode = DisplayMode.SbsFull; if (item.getTitle().equals(getString(R.string.action_sbs_cross))) { mDisplaySwap = true; } else { mDisplaySwap = false; } item.setChecked(true); updateDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); } break; case R.id.action_3dtv: if (!item.isChecked()) { mDisplayMode = DisplayMode.SbsHalf; item.setChecked(true); updateDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); } break; case R.id.action_swap: if (item.isChecked()) { mDisplaySwap = false; item.setChecked(false); } else { mDisplaySwap = true; item.setChecked(true); } updateDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); break; case R.id.action_grayscale: if (item.isChecked()) { mDisplayColor = true; item.setChecked(false); } else { mDisplayColor = false; item.setChecked(true); } updateDisplayPrefs(); invalidateOptionsMenu(); switchDisplayMode(); break; default: return super.onOptionsItemSelected(item); } return true; } /** * Handles activity callbacks. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == ActivityCode.SHARE_FILE.getValue()) { if (resultCode == RESULT_OK) { shareMedia(data); } else { Toast.makeText(getApplication(), getString(R.string.error_network_error), Toast.LENGTH_LONG).show(); } } } /** * start the ShareFileActivity to create an empty file in google drive, * and returned the link here. */ private void createDriveLink() { String shareType = null; if (!(new File(mMediaPath)).exists()) { /* * something's wrong */ if (BuildConfig.DEBUG) Log.e(TAG, "createCloudLink: " + mMediaPath + " not found"); return; } Intent intent = new Intent(GalleryActivity.this, UploadFileActivity.class); intent.putExtra(MainConsts.EXTRA_PATH, mMediaPath); /* * filename: SBS_<refCode>_<index>.jpg * MPG_<refCode>_<index>.cvr */ if (MediaScanner.isMovie(mMediaPath)) { shareType = "movie"; mPreviewPath = MediaScanner.getPreviewPathFromMediaPath(mMediaPath); mPreviewName = (new File(mPreviewPath)).getName(); if (!(new File(mPreviewPath).exists())) { /* * sanity check */ if (BuildConfig.DEBUG) if (VERBOSE) Log.e(TAG, "createDriveLink: preview frame not found " + mPreviewPath); mPreviewPath = null; mPreviewName = null; } } else { shareType = "photo"; /* * non-chained upload */ mPreviewPath = null; mPreviewName = null; } intent.putExtra(MainConsts.EXTRA_PATH_PREVIEW, mPreviewPath); startActivityForResult(intent, ActivityCode.SHARE_FILE.getValue()); Bundle bundle = new Bundle(); bundle.putString(MainConsts.FIREBASE_CUSTOM_PARAM_SHARE_TYPE, shareType); mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_SHARE_START, bundle); } /** * share only to certain apps. code based on "http://stackoverflow.com/questions/ * 9730243/how-to-filter-specific-apps-for-action-send-intent-and-set-a-different- * text-for/18980872#18980872" * * "copy link" inspired by http://cketti.de/2016/06/15/share-url-to-clipboard/ * * in general, "deep linking" is supported by the apps below. Facebook, Wechat, * Telegram are exceptions. click on the link would bring users to the landing * page. * * Facebook doesn't take our EXTRA_TEXT so user will have to "copy link" first * then paste the link */ private void shareMedia(Intent data) { /* * we log this as "share complete", but user can still cancel the share at this point, * and we wouldn't be able to know */ mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_SHARE_COMPLETE, null); Resources resources = getResources(); /* * get the resource id for the shared file */ String id = data.getStringExtra(MainConsts.EXTRA_ID_RESOURCE); /* * construct link */ String link = "https://" + resources.getString(R.string.app_domain) + "/?id=" + id + "&name=" + ((mPreviewName != null) ? mPreviewName : mMediaName); /* * message subject and text */ String emailSubject, emailText, twitterText; if (MediaScanner.isPhoto(mMediaPath)) { emailSubject = resources.getString(R.string.emailSubjectPhoto); emailText = resources.getString(R.string.emailBodyPhotoPrefix) + link; twitterText = resources.getString(R.string.emailBodyPhotoPrefix) + link + resources.getString(R.string.twitterHashtagPhoto) + resources.getString(R.string.app_hashtag); } else if (MediaScanner.is3dMovie(mMediaPath)) { emailSubject = resources.getString(R.string.emailSubjectVideo); emailText = resources.getString(R.string.emailBodyVideoPrefix) + link; twitterText = resources.getString(R.string.emailBodyVideoPrefix) + link + resources.getString(R.string.twitterHashtagVideo) + resources.getString(R.string.app_hashtag); } else //if(MediaScanner.is2dMovie(mMediaPath)) { emailSubject = resources.getString(R.string.emailSubjectVideo2d); emailText = resources.getString(R.string.emailBodyVideoPrefix2d) + link; twitterText = resources.getString(R.string.emailBodyVideoPrefix2d) + link + resources.getString(R.string.twitterHashtagVideo) + resources.getString(R.string.app_hashtag); } Intent emailIntent = new Intent(); emailIntent.setAction(Intent.ACTION_SEND); // Native email client doesn't currently support HTML, but it doesn't hurt to try in case they fix it emailIntent.putExtra(Intent.EXTRA_SUBJECT, emailSubject); emailIntent.putExtra(Intent.EXTRA_TEXT, emailText); //emailIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_native))); emailIntent.setType("message/rfc822"); PackageManager pm = getPackageManager(); Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType("text/plain"); Intent openInChooser = Intent.createChooser(emailIntent, resources.getString(R.string.share_chooser_text)); List<ResolveInfo> resInfo = pm.queryIntentActivities(sendIntent, 0); List<LabeledIntent> intentList = new ArrayList<LabeledIntent>(); for (int i = 0; i < resInfo.size(); i++) { // Extract the label, append it, and repackage it in a LabeledIntent ResolveInfo ri = resInfo.get(i); String packageName = ri.activityInfo.packageName; if (packageName.contains("android.email")) { emailIntent.setPackage(packageName); } else if (packageName.contains("twitter") || packageName.contains("facebook") || packageName.contains("whatsapp") || packageName.contains("tencent.mm") || //wechat packageName.contains("line") || packageName.contains("skype") || packageName.contains("viber") || packageName.contains("kik") || packageName.contains("sgiggle") || //tango packageName.contains("kakao") || packageName.contains("telegram") || packageName.contains("nimbuzz") || packageName.contains("hike") || packageName.contains("imoim") || packageName.contains("bbm") || packageName.contains("threema") || packageName.contains("mms") || packageName.contains("android.apps.messaging") || //google messenger packageName.contains("android.talk") || //google hangouts packageName.contains("android.gm")) { Intent intent = new Intent(); intent.setComponent(new ComponentName(packageName, ri.activityInfo.name)); intent.setAction(Intent.ACTION_SEND); intent.setType("text/plain"); if (packageName.contains("twitter")) { intent.putExtra(Intent.EXTRA_TEXT, twitterText); } else if (packageName.contains("facebook")) { /* * the warning below is wrong! at least on GS5, Facebook client does take * our text, however it seems it takes only the first hyperlink in the * text. * * Warning: Facebook IGNORES our text. They say "These fields are intended * for users to express themselves. Pre-filling these fields erodes the * authenticity of the user voice." * One workaround is to use the Facebook SDK to post, but that doesn't * allow the user to choose how they want to share. We can also make a * custom landing page, and the link will show the <meta content ="..."> * text from that page with our link in Facebook. */ intent.putExtra(Intent.EXTRA_TEXT, link); } else if (packageName.contains("tencent.mm")) //wechat { /* * wechat appears to do this similar to Facebook */ intent.putExtra(Intent.EXTRA_TEXT, link); } else if (packageName.contains("android.gm")) { // If Gmail shows up twice, try removing this else-if clause and the reference to "android.gm" above intent.putExtra(Intent.EXTRA_SUBJECT, emailSubject); intent.putExtra(Intent.EXTRA_TEXT, emailText); //intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_gmail))); intent.setType("message/rfc822"); } else if (packageName.contains("android.apps.docs")) { /* * google drive - no reason to send link to it */ continue; } else { intent.putExtra(Intent.EXTRA_TEXT, emailText); } intentList.add(new LabeledIntent(intent, packageName, ri.loadLabel(pm), ri.icon)); } } /* * create "Copy Link To Clipboard" Intent */ Intent clipboardIntent = new Intent(this, CopyToClipboardActivity.class); clipboardIntent.setData(Uri.parse(link)); intentList.add(new LabeledIntent(clipboardIntent, getPackageName(), getResources().getString(R.string.clipboard_activity_name), R.drawable.ic_copy_link)); // convert intentList to array LabeledIntent[] extraIntents = intentList.toArray(new LabeledIntent[intentList.size()]); openInChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents); startActivity(openInChooser); } private void shareUpdate(int position) { if ((mMediaList != null) && (position >= 0) && (position <= mMediaList.size())) { mMediaPath = mMediaList.get(position); mMediaName = (new File(mMediaPath)).getName(); } else { mMediaPath = null; mMediaName = null; } } @Override public boolean onMenuOpened(int featureId, Menu menu) { /* * if the overflow menu was opened, we will hide action bar * after a longer delay, to allow user enough time to see * what's in the overflow menu */ if (featureId == Window.FEATURE_ACTION_BAR) { if (AUTO_HIDE) { delayedHide(AUTO_HIDE_DELAY_LONG_MILLIS); } } /* * opening menu apparently reset the full screen flags. so * redo it here. */ //forceFullScreen(); return super.onMenuOpened(featureId, menu); } @SuppressWarnings("deprecation") private void initViewPager() { if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "initViewPager"); Display display = getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); mViewPager = (ViewPager) findViewById(R.id.view_pager); mViewPager.setAdapter(new MediaPagerAdapter(this, size.x, size.y)); mViewPager.setOnTouchListener(otl); mViewPager.setOnPageChangeListener(opcl); mViewPager.setOffscreenPageLimit(MAX_PAGE); } /** * delete current file */ private void deleteFile() { if (mViewPager.getAdapter().getCount() == 0) { if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "deleteFile: nothing to delete"); return; } final int index = mViewPager.getCurrentItem(); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle); alertDialogBuilder.setTitle(R.string.delete); alertDialogBuilder.setMessage(R.string.warning_item_delete); alertDialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { /* * delete sbs/cvr, thumbnail, preview and exported files */ String mediaFilePath = mMediaList.get(index); MediaScanner.deleteFile(mediaFilePath); updateViewPager(null, index, -1); } }); alertDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //do nothing } }); AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); } public void switchDisplayMode() { if ((mMediaList == null) || (mMediaList.size() == 0)) { finish(); return; } final int index = mViewPager.getCurrentItem(); if (index == -1) { return; } /* * if current image is deleted in ThumbView, then index * may go out of bound */ final int i = Math.min(index, mMediaList.size() - 1); if (mDisplayMode == DisplayMode.Cardboard) { Intent intent; String filePath = mMediaList.get(i); if (MediaScanner.isMovie(filePath)) { mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_VIEW_MOVIE, null); if (!MediaScanner.isEmpty(filePath)) { intent = new Intent(this, MovieActivity.class); } else { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle); alertDialogBuilder.setTitle(R.string.information); alertDialogBuilder.setMessage(R.string.info_download_first); alertDialogBuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { } }); AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); return; } } else { mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_VIEW_PHOTO, null); intent = new Intent(this, PhotoActivity.class); intent.putExtra(MainConsts.EXTRA_COLOR, mDisplayColor); } intent.setData(Uri.fromFile(new File(filePath))); intent.putExtra(MainConsts.EXTRA_MSG, mIsMyMedia); startActivity(intent); return; } MediaPagerAdapter mpa = (MediaPagerAdapter) mViewPager.getAdapter(); /* * calling below, plus the fact we have getItemPosition returning POSITION_NONE * will cause all pages to be redrawn. c.f. http://stackoverflow.com/questions/ * 7263291/viewpager-pageradapter-not-updating-the-view/8024557#8024557 */ mpa.notifyDataSetChanged(); new Handler().post(new Runnable() { @Override public void run() { mViewPager.setCurrentItem(i, true); shareUpdate(i); } }); } /** * updates the view pager by either path name OR index. either path should be * non-null, or index should be other than -1. if this is a movie download * progress update, percentage will be different from -1. */ private void updateViewPager(String path, int index, int percentage) { ArrayList<String> newMediaList = MediaScanner.getMediaList(mIsMyMedia); if (BuildConfig.DEBUG) Log.d(TAG, "updateViewPager: newMediaList size=" + newMediaList.size()); if (mMediaList != null) { if (BuildConfig.DEBUG) Log.d(TAG, "updateViewPager: current mediaList size=" + mMediaList.size()); } if (newMediaList.size() == 0) { /* * sanity check */ finish(); return; } if (!MediaScanner.isContentSame(newMediaList, mMediaList)) { /* * we have additions or subtractions in our list. in this case, we * will update the entire view pager. */ mMediaList = newMediaList; if (path != null) { index = mMediaList.indexOf(path); } else { /* * if current image is deleted in ThumbView, then index * may go out of bound */ index = Math.min(index, mMediaList.size() - 1); } MediaPagerAdapter mpa = (MediaPagerAdapter) mViewPager.getAdapter(); mpa.setMedia(mMediaList); mpa.setIsMyMedia(mIsMyMedia); mpa.notifyDataSetChanged(); /* * if we want to change the data and then go to a specific position, * we'd have to do this or we will have a very buggy behavior, c.f. * "http://stackoverflow.com/questions/12008716/setcurrentitem-in- * viewpager-not-scroll-immediately-in-the-correct-position" */ final int i = index; if (i != -1) { new Handler().post(new Runnable() { @Override public void run() { mViewPager.setCurrentItem(i, true); shareUpdate(i); } }); } else { if (BuildConfig.DEBUG) Log.e(TAG, "updateViewPager: error finding path=" + path); } } else { /* * we have status update to a member of our current list, identified by * its path. in this case, we find the relevant view in the view pager * and update it. */ FrameLayout fl = null; if (path != null) { fl = (FrameLayout) (mViewPager.findViewWithTag(path)); } else { if (BuildConfig.DEBUG) Log.e(TAG, "updateViewPager: trying to update an " + "existing view but path unspecified."); /* * we cannot update a view identified by its index in the view pager. code * below doesn't work. "c.f. http://stackoverflow.com/questions/12854783/ * android-viewpager-get-the-current-view" */ //fl = (FrameLayout) (mViewPager.getChildAt(index)); //path = (String) fl.getTag(); } if ((fl != null) && (path != null)) { if (BuildConfig.DEBUG) Log.d(TAG, "updateViewPager: invalidate view"); ((MediaPagerAdapter) mViewPager.getAdapter()).refreshItem(fl, path, percentage); } } showHideView(); } private void switchThumbView() { /* * note we use FLAG_ACTIVITY_CLEAR_TOP below which means if we came * here from Camera/CamcorderActivity, then they will be destroyed * once we return to main. this currently won't happen as we always * go from Camera/Camcorder to Photo/MovieActivity directly, without * going through gallery, but in the future if we do that, we will * need to revisit below */ Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(MainConsts.EXTRA_MSG, mIsMyMedia); startActivity(intent); } private void showHideView() { if ((mMediaList == null) || (mMediaList.size() == 0)) { finish(); return; } mViewPager.setVisibility(View.VISIBLE); mNoMedia.setVisibility(View.GONE); } public DisplayMode getDisplayMode() { return mDisplayMode; } public boolean getDisplayColor() { return mDisplayColor; } public boolean getDisplaySwap() { return mDisplaySwap; } public void onDownloadFileStarted(String name) { if (BuildConfig.DEBUG) Log.d(TAG, "onDownloadFileStarted"); mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_DOWNLOAD_FILE_STARTED, null); } public void onDownloadFileComplete(String name) { if (BuildConfig.DEBUG) Log.d(TAG, "onDownloadFileComplete"); mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_DOWNLOAD_FILE_COMPLETE, null); removeDownloadFileFragment(name); } public void onDownloadFileFailure(String name) { if (BuildConfig.DEBUG) Log.d(TAG, "onDownloadFileFailure"); mFirebaseAnalytics.logEvent(MainConsts.FIREBASE_CUSTOM_EVENT_DOWNLOAD_FILE_FAILURE, null); if (!MediaScanner.isMovie(name)) { /* * if this is a preview frame or photo, we need to delete the placeholder * file, because we would have nothing to show to the user * * if we failed on cvr download, leave the placeholder file alone. */ String sharePath = MediaScanner.getSharedMediaPathFromOrigName(name); MediaScanner.removeItemMediaList(sharePath); (new File(sharePath)).delete(); } updateViewPager(null, mViewPager.getCurrentItem(), -1); removeDownloadFileFragment(name); } public void onDownloadFileProgress(String name, final int downloadBytes, final int totalBytes) { if (MediaScanner.isMovie(name)) { if (totalBytes != -1) { int percentage = (int) ((float) downloadBytes * 100 / (float) totalBytes); if (BuildConfig.DEBUG) Log.d(TAG, "onDownloadFileProgress: " + percentage + "%"); String path = MediaScanner.getSharedMediaPathFromOrigName(name); Intent messageIntent = new Intent(MainConsts.FILE_DOWNLOADER_MESSAGE); messageIntent.putExtra(MainConsts.EXTRA_WHAT, MainConsts.MSG_FILE_DOWNLOADER_PROGRESS); messageIntent.putExtra(MainConsts.EXTRA_PATH, path); messageIntent.putExtra(MainConsts.EXTRA_MSG, percentage); LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); } } } private void addDownloadFileFragment(String resId, String name, int size) { try { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); Bundle bundle = new Bundle(); bundle.putString(MainConsts.EXTRA_ID_RESOURCE, resId); bundle.putString(MainConsts.EXTRA_NAME, name); bundle.putInt(MainConsts.EXTRA_SIZE, size); DownloadFileFragment ddf = new DownloadFileFragment(); ddf.setArguments(bundle); ddf.setRetainInstance(true); fragmentTransaction.add(R.id.display_panel, ddf, name); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { /* * we could potentially have the same kind of exception we saw * when we remove fragment in the midst of screen rotation. it * is not clear if/when this happens, if the fragment will be * created/added. */ if (BuildConfig.DEBUG) Log.e(TAG, "addDownloadFileFragment: commit failed " + e.getMessage()); } } private void removeDownloadFileFragment(String name) { /* * we do not need the fragment anymore. remove it. */ try { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); Fragment fragment = fragmentManager.findFragmentByTag(name); fragmentTransaction.remove(fragment); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { /* * observed exception when we remove while at the same time doing a screen * rotation. this happens despite we call commitAllowingStateLoss instead * of commit. if we encounter this occasionally, it's not a big deal - the * fragment will eventually be destroyed when we exit the activity. * * it is not clear if the remove is successful (thus memory be freed) if * commit fails */ if (BuildConfig.DEBUG) Log.e(TAG, "removeDownloadFileFragment: commit failed " + e.getMessage()); } } /** * Checks whether the device currently has a network connection. * @return true if the device has a network connection, false otherwise. */ private boolean isDeviceOnline() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); } private void saveDriveFileRecord(String filename, String resId) { Context context = getApplicationContext(); SharedPreferences driveFileRecord = context .getSharedPreferences(context.getString(R.string.drive_file_record), Context.MODE_PRIVATE); if (driveFileRecord != null) { SharedPreferences.Editor editor = driveFileRecord.edit(); editor.putString(filename, resId); editor.commit(); } } public static String fixedLengthHumanReadableByteCount(long bytes, boolean si, int length) { String hr = humanReadableByteCount(bytes, si); String spaces = ""; for (int i = 0; i < (length - hr.length()); i++) { spaces += "."; } return spaces + hr; } public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); return String.format("%3.1f %sB", bytes / Math.pow(unit, exp), pre); } private class MediaPagerAdapter extends PagerAdapter { private final WeakReference<GalleryActivity> mActivityRef; private ArrayList<String> mMediaList = new ArrayList<String>(); private boolean mIsMyMedia; private int mScreenWidth, mScreenHeight; private DisplayMetrics mDisplayMetrics; private Drawable mPlayButtonDrawable; private Drawable mDownloadButtonDrawable; private Drawable m3dIconDrawable; public MediaPagerAdapter(GalleryActivity activity, int width, int height) { mScreenWidth = width; mScreenHeight = height; mDisplayMetrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(mDisplayMetrics); mActivityRef = new WeakReference<GalleryActivity>(activity); mPlayButtonDrawable = mActivityRef.get().getResources() .getDrawable(R.drawable.ic_play_circle_outline_white_24dp); mDownloadButtonDrawable = mActivityRef.get().getResources() .getDrawable(R.drawable.ic_cloud_download_white_24dp); m3dIconDrawable = mActivityRef.get().getResources().getDrawable(R.drawable.ic_3d_icon); } public void setMedia(ArrayList<String> list) { mMediaList = list; } public void setIsMyMedia(boolean isMyMedia) { mIsMyMedia = isMyMedia; } @Override public int getCount() { return mMediaList.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == ((FrameLayout) object); } @Override public Object instantiateItem(ViewGroup container, int position) { GalleryActivity activity; String path = mMediaList.get(position); if (mActivityRef != null) { activity = mActivityRef.get(); } else { if (BuildConfig.DEBUG) Log.e(TAG, "instantiateItem: activity ref null"); return null; } FrameLayout fl = new FrameLayout(activity); fl.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); fl.setTag(path); /* * image view for holding the photo or video preview frame */ ImageView imageView = new ImageView(activity); imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setImageDrawable(null); imageView.setVisibility(View.VISIBLE); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER; /* * progress bar while downloading/decoding/processing */ ProgressBar progBar = new ProgressBar(activity, null, android.R.attr.progressBarStyleLarge); progBar.setLayoutParams(layoutParams); progBar.setVisibility(View.VISIBLE); TextView progText = new TextView(activity); progText.setLayoutParams(layoutParams); progText.setTextAppearance(activity, R.style.ProgText); progText.setVisibility(View.VISIBLE); /* * add imageView and progress bar */ fl.addView(imageView); fl.addView(progBar); fl.addView(progText); if (MediaScanner.isMovie(path)) { /* * download/play button - only visible for video */ ImageButton downloadPlayButton = new ImageButton(activity); downloadPlayButton.setLayoutParams(layoutParams); downloadPlayButton.setScaleType(ImageButton.ScaleType.FIT_CENTER); downloadPlayButton.setOnClickListener(new MovieOnClickListener(path, progBar, progText)); downloadPlayButton.setBackground(null); fl.addView(downloadPlayButton); } if (!MediaScanner.is2d(path)) { int iconDim = Math.min(mScreenHeight, mScreenWidth) / 8; layoutParams = new FrameLayout.LayoutParams(iconDim, iconDim); layoutParams.gravity = Gravity.TOP | Gravity.START; /* * 3d icon */ layoutParams.setMargins(0, iconDim, 0, 0); //just below actionbar ImageView modeIconView = new ImageView(activity); modeIconView.setLayoutParams(layoutParams); modeIconView.setImageDrawable(m3dIconDrawable); fl.addView(modeIconView); } refreshItem(fl, path, -1); ((ViewPager) container).addView(fl, 0); return fl; } @Override public void destroyItem(ViewGroup container, int position, Object object) { /* * recycle bitmap */ ImageView imageView = (ImageView) ((FrameLayout) object).getChildAt(0); BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable(); if (bd != null) { Bitmap bitmap = bd.getBitmap(); if (bitmap != null) { if (BuildConfig.DEBUG) if (VERBOSE) Log.d(TAG, "destroying bitmap at position " + position); bitmap.recycle(); imageView.setImageBitmap(null); } bd.setCallback(null); } ((ViewPager) container).removeView((FrameLayout) object); } /** * returning POSITION_NONE - when you call notifyDataSetChanged(), the view pager * will remove all views and reload them all. So the reload effect is obtained. * c.f. "http://stackoverflow.com/questions/7263291/viewpager-pageradapter-not- * updating-the-view" * * we may need to use the more efficient updating method as suggested by Alvaro * Luis Bustamante in that same post */ @Override public int getItemPosition(Object object) { return POSITION_NONE; } public void refreshItem(FrameLayout fl, String path, int percentage) { GalleryActivity activity; DisplayMode displayMode; boolean displayColor, displaySwap; activity = mActivityRef.get(); displayMode = activity.getDisplayMode(); displayColor = activity.getDisplayColor(); displaySwap = activity.getDisplaySwap(); ProgressBar progBar = (ProgressBar) fl.getChildAt(1); TextView progText = (TextView) fl.getChildAt(2); if (MediaScanner.isMovie(path)) { ImageButton downloadPlayButton = (ImageButton) fl.getChildAt(3); if (percentage != -1) { /* * this is a progress update refresh. just set the percentage * and return. if we go through the rest of the logic, the * progress bar would be stopped then restarted causing some * visual problems */ progText.setText(percentage + "%"); return; } String previewPath = MediaScanner.getPreviewPathFromMediaPath(path); if (!MediaScanner.isEmpty(previewPath)) { downloadPlayButton.setVisibility(View.VISIBLE); progBar.setVisibility(View.GONE); progText.setVisibility(View.GONE); if (BuildConfig.DEBUG) Log.d(TAG, "refreshItem: decode sbs path=" + previewPath); new DecodePreviewTask(fl, previewPath, displayMode, displayColor, displaySwap, mScreenWidth, mScreenHeight).execute(); } else { downloadPlayButton.setVisibility(View.GONE); progBar.setVisibility(View.VISIBLE); progText.setVisibility(View.VISIBLE); if (!mIsMyMedia) { Intent serviceIntent = new Intent(activity, FileDownloaderService.class); serviceIntent.putExtra(MainConsts.EXTRA_PATH, previewPath); startService(serviceIntent); } } if (mIsMyMedia) { downloadPlayButton.setImageDrawable(mPlayButtonDrawable); } else { if (!MediaScanner.isEmpty(path)) { downloadPlayButton.setImageDrawable(mPlayButtonDrawable); } else if (!isDownloading(path)) { downloadPlayButton.setImageDrawable(mDownloadButtonDrawable); } else { downloadPlayButton.setVisibility(View.GONE); progBar.setVisibility(View.VISIBLE); progText.setVisibility(View.VISIBLE); } } } else { if (!MediaScanner.isEmpty(path)) { progBar.setVisibility(View.GONE); progText.setVisibility(View.GONE); if (BuildConfig.DEBUG) Log.d(TAG, "refreshItem: decode sbs path=" + path); new DecodePreviewTask(fl, path, displayMode, displayColor, displaySwap, mScreenWidth, mScreenHeight).execute(); } else { progBar.setVisibility(View.VISIBLE); progText.setVisibility(View.GONE); if (!mIsMyMedia) { Intent serviceIntent = new Intent(activity, FileDownloaderService.class); serviceIntent.putExtra(MainConsts.EXTRA_PATH, path); startService(serviceIntent); } } } fl.invalidate(); } private boolean isDownloading(String filePath) { return (new File(filePath + ".tmp")).exists(); /* * originally we pass a downloading list when updating the view pager, * and we rely on that list to determine if a file is being downloaded. * but that don't work as updateViewPager may not be called */ } /* private void getCreatorInfo(String path) { if(MediaScanner.isMovie(path)) { path = getThumbPath(path); } String info = MediaScanner.extractExifInfo(path); String creatorName = null; String creatorPhotoUrl = null; if(info != null) { int offset1 = info.indexOf("name="); int offset2 = info.indexOf("photourl="); if((offset1 != -1) && (offset2 != -1)) { creatorName = info.substring(offset1+5, offset2); creatorPhotoUrl = info.substring(offset2); } } if(BuildConfig.DEBUG) Log.e(TAG, "getCreatorInfo: " + "creatorName=" + creatorName + ", creatorPhotoUrl=" + creatorPhotoUrl); } */ private class MovieOnClickListener implements OnClickListener { String path; ProgressBar progBar; TextView progText; public MovieOnClickListener(String path, ProgressBar progBar, TextView progText) { this.path = path; this.progBar = progBar; this.progText = progText; } @Override public void onClick(View v) { GalleryActivity activity = mActivityRef.get(); boolean displayColor = activity.getDisplayColor(); if (!isDownloading(path) && !MediaScanner.isEmpty(path)) { Intent intent; if (MediaScanner.isMovie(path)) { intent = new Intent(activity, MovieActivity.class); } else { intent = new Intent(activity, PhotoActivity.class); intent.putExtra(MainConsts.EXTRA_COLOR, displayColor); } intent.setData(Uri.fromFile(new File(path))); intent.putExtra(MainConsts.EXTRA_MSG, mIsMyMedia); activity.startActivity(intent); } else if (!mIsMyMedia && !isDownloading(path)) { if (!startChainedDownload()) { Toast.makeText(activity, R.string.error_incorrect_link, Toast.LENGTH_LONG).show(); } else { //trigger progress bar v.setVisibility(View.GONE); progBar.setVisibility(View.VISIBLE); progText.setVisibility(View.VISIBLE); } } } /** * start chained download based on resId found in the preview frame * exif header. format of chain info: "resId=...size=..." */ private boolean startChainedDownload() { String info = MediaScanner.extractExifInfo(MediaScanner.getPreviewPathFromMediaPath(path)); if (BuildConfig.DEBUG) Log.d(TAG, "chain info=" + info); if (info == null) { if (BuildConfig.DEBUG) Log.e(TAG, "startChainedDownload: chain info not available"); return false; } String resId = null; int size = -1; int offset1 = -1; int offset2 = -1; offset1 = info.indexOf("resId="); offset2 = info.indexOf("size="); if ((offset1 != -1) && (offset2 != -1)) { resId = info.substring(offset1 + 6, offset2); try { size = Integer.parseInt(info.substring(offset2 + 5)); } catch (Exception e) { if (BuildConfig.DEBUG) Log.e(TAG, "startChainedDownload: chain info wrong, " + e.getMessage()); return false; } } else { if (BuildConfig.DEBUG) Log.e(TAG, "startChainedDownload: chain info wrong"); return false; } addDownloadFileFragment(resId, (new File(path)).getName(), size); return true; } }; } }