remove media scan and file views

This commit is contained in:
dkanada 2019-07-16 00:05:17 -07:00
commit 0d6a0929b9
46 changed files with 4 additions and 2026 deletions

View file

@ -1,169 +0,0 @@
package com.kabouzeid.gramophone.dialogs;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.DialogFragment;
import android.view.View;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.misc.UpdateToastMediaScannerCompletionListener;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.folders.FoldersFragment;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author Aidan Follestad (afollestad), modified by Karim Abou Zeid
*/
public class ScanMediaFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
String initialPath = PreferenceUtil.getInstance(getContext()).getStartDirectory().getAbsolutePath();
private File parentFolder;
private File[] parentContents;
private boolean canGoUp = false;
public static ScanMediaFolderChooserDialog create() {
return new ScanMediaFolderChooserDialog();
}
private static void scanPaths(@NonNull WeakReference<Activity> activityWeakReference, @NonNull Context applicationContext, @Nullable String[] toBeScanned) {
Activity activity = activityWeakReference.get();
if (toBeScanned == null || toBeScanned.length < 1) {
Toast.makeText(applicationContext, R.string.nothing_to_scan, Toast.LENGTH_SHORT).show();
} else {
MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, activity != null ? new UpdateToastMediaScannerCompletionListener(activity, toBeScanned) : null);
}
}
private String[] getContentsArray() {
if (parentContents == null) {
if (canGoUp) {
return new String[]{".."};
}
return new String[]{};
}
String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
if (canGoUp) {
results[0] = "..";
}
for (int i = 0; i < parentContents.length; i++) {
results[canGoUp ? i + 1 : i] = parentContents[i].getName();
}
return results;
}
private File[] listFiles() {
File[] contents = parentFolder.listFiles();
List<File> results = new ArrayList<>();
if (contents != null) {
for (File fi : contents) {
if (fi.isDirectory()) {
results.add(fi);
}
}
Collections.sort(results, new FolderSorter());
return results.toArray(new File[results.size()]);
}
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
return new MaterialDialog.Builder(getActivity())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(android.R.string.ok)
.build();
}
if (savedInstanceState == null) {
savedInstanceState = new Bundle();
}
if (!savedInstanceState.containsKey("current_path")) {
savedInstanceState.putString("current_path", initialPath);
}
parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
checkIfCanGoUp();
parentContents = listFiles();
MaterialDialog.Builder builder =
new MaterialDialog.Builder(getActivity())
.title(parentFolder.getAbsolutePath())
.items((CharSequence[]) getContentsArray())
.itemsCallback(this)
.autoDismiss(false)
.onPositive((dialog, which) -> {
final Context applicationContext = getActivity().getApplicationContext();
final WeakReference<Activity> activityWeakReference = new WeakReference<>(getActivity());
dismiss();
new FoldersFragment.ArrayListPathsAsyncTask(getActivity(), paths -> scanPaths(activityWeakReference, applicationContext, paths)).execute(new FoldersFragment.ArrayListPathsAsyncTask.LoadingInfo(parentFolder, FoldersFragment.AUDIO_FILE_FILTER));
})
.onNegative((materialDialog, dialogAction) -> dismiss())
.positiveText(R.string.action_scan_directory)
.negativeText(android.R.string.cancel);
return builder.build();
}
@Override
public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
if (canGoUp && i == 0) {
parentFolder = parentFolder.getParentFile();
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = parentFolder.getParentFile();
}
checkIfCanGoUp();
} else {
parentFolder = parentContents[canGoUp ? i - 1 : i];
canGoUp = true;
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = Environment.getExternalStorageDirectory();
}
}
reload();
}
private void checkIfCanGoUp() {
canGoUp = parentFolder.getParent() != null;
}
private void reload() {
parentContents = listFiles();
MaterialDialog dialog = (MaterialDialog) getDialog();
dialog.setTitle(parentFolder.getAbsolutePath());
dialog.setItems((CharSequence[]) getContentsArray());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_path", parentFolder.getAbsolutePath());
}
private static class FolderSorter implements Comparator<File> {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View file

@ -1,53 +0,0 @@
package com.kabouzeid.gramophone.misc;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import java.lang.ref.WeakReference;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
public class UpdateToastMediaScannerCompletionListener implements MediaScannerConnection.OnScanCompletedListener {
private int scanned = 0;
private int failed = 0;
private final String[] toBeScanned;
private final String scannedFiles;
private final String couldNotScanFiles;
private Toast toast;
private final WeakReference<Activity> activityWeakReference;
@SuppressLint("ShowToast")
public UpdateToastMediaScannerCompletionListener(Activity activity, String[] toBeScanned) {
this.toBeScanned = toBeScanned;
scannedFiles = activity.getString(R.string.scanned_files);
couldNotScanFiles = activity.getString(R.string.could_not_scan_files);
toast = Toast.makeText(activity.getApplicationContext(), "", Toast.LENGTH_SHORT);
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void onScanCompleted(final String path, final Uri uri) {
Activity activity = activityWeakReference.get();
if (activity != null) {
activity.runOnUiThread(() -> {
if (uri == null) {
failed++;
} else {
scanned++;
}
String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : "");
toast.setText(text);
toast.show();
});
}
}
}

View file

@ -18,7 +18,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
@ -27,7 +26,6 @@ import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.appthemehelper.util.NavigationViewUtil;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.dialogs.ScanMediaFolderChooserDialog;
import com.kabouzeid.gramophone.glide.SongGlideRequest;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.SearchQueryHelper;
@ -37,10 +35,8 @@ import com.kabouzeid.gramophone.loader.PlaylistSongLoader;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.folders.FoldersFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.library.LibraryFragment;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
@ -51,12 +47,8 @@ import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AbsSlidingMusicPanelActivity {
public static final String TAG = MainActivity.class.getSimpleName();
private static final int LIBRARY = 0;
private static final int FOLDERS = 1;
@BindView(R.id.navigation_view)
NavigationView navigationView;
@BindView(R.id.drawer_layout)
@ -81,31 +73,12 @@ public class MainActivity extends AbsSlidingMusicPanelActivity {
setUpDrawerLayout();
if (savedInstanceState == null) {
setMusicChooser(PreferenceUtil.getInstance(this).getLastMusicChooser());
setCurrentFragment(LibraryFragment.newInstance());
} else {
restoreCurrentFragment();
}
}
private void setMusicChooser(int key) {
if (!App.isProVersion() && key == FOLDERS) {
Toast.makeText(this, R.string.folder_view_is_a_pro_feature, Toast.LENGTH_LONG).show();
key = LIBRARY;
}
PreferenceUtil.getInstance(this).setLastMusicChooser(key);
switch (key) {
case LIBRARY:
navigationView.setCheckedItem(R.id.nav_library);
setCurrentFragment(LibraryFragment.newInstance());
break;
case FOLDERS:
navigationView.setCheckedItem(R.id.nav_folders);
setCurrentFragment(FoldersFragment.newInstance(this));
break;
}
}
private void setCurrentFragment(@SuppressWarnings("NullableProblems") Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, null).commit();
currentFragment = (MainActivityFragmentCallbacks) fragment;
@ -134,20 +107,12 @@ public class MainActivity extends AbsSlidingMusicPanelActivity {
drawerLayout.closeDrawers();
switch (menuItem.getItemId()) {
case R.id.nav_library:
new Handler().postDelayed(() -> setMusicChooser(LIBRARY), 200);
break;
case R.id.nav_folders:
new Handler().postDelayed(() -> setMusicChooser(FOLDERS), 200);
navigationView.setCheckedItem(R.id.nav_library);
setCurrentFragment(LibraryFragment.newInstance());
break;
case R.id.buy_pro:
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, PurchaseActivity.class)), 200);
break;
case R.id.action_scan:
new Handler().postDelayed(() -> {
ScanMediaFolderChooserDialog dialog = ScanMediaFolderChooserDialog.create();
dialog.show(getSupportFragmentManager(), "SCAN_MEDIA_FOLDER_CHOOSER");
}, 200);
break;
case R.id.nav_settings:
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, SettingsActivity.class)), 200);
break;

View file

@ -50,7 +50,7 @@ public class SplashActivity extends AbsBaseActivity {
connectionManager.Connect(credentialProvider.GetCredentials().getServers().get(0), new Response<ConnectionResult>() {
@Override
public void onResponse(ConnectionResult result) {
if (result.getState() != ConnectionState.SignedIn) return;
//if (result.getState() != ConnectionState.SignedIn) return;
App.setApiClient(result.getApiClient());
context.startActivity(new Intent(context, MainActivity.class));
}

View file

@ -1,691 +0,0 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivity.folders;
import android.app.Dialog;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.PopupMenu;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.snackbar.Snackbar;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.common.ATHToolbarActivity;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.SongFileAdapter;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper;
import com.kabouzeid.gramophone.interfaces.CabHolder;
import com.kabouzeid.gramophone.interfaces.LoaderIds;
import com.kabouzeid.gramophone.misc.DialogAsyncTask;
import com.kabouzeid.gramophone.misc.UpdateToastMediaScannerCompletionListener;
import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.MainActivity;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.AbsMainActivityFragment;
import com.kabouzeid.gramophone.util.FileUtil;
import com.kabouzeid.gramophone.util.PhonographColorUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.kabouzeid.gramophone.views.BreadCrumbLayout;
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import java.io.File;
import java.io.FileFilter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class FoldersFragment extends AbsMainActivityFragment implements MainActivity.MainActivityFragmentCallbacks, CabHolder, BreadCrumbLayout.SelectionCallback, SongFileAdapter.Callbacks, AppBarLayout.OnOffsetChangedListener, LoaderManager.LoaderCallbacks<List<File>> {
private static final int LOADER_ID = LoaderIds.FOLDERS_FRAGMENT;
protected static final String PATH = "path";
protected static final String CRUMBS = "crumbs";
private Unbinder unbinder;
@BindView(R.id.coordinator_layout)
CoordinatorLayout coordinatorLayout;
@BindView(R.id.container)
View container;
@BindView(android.R.id.empty)
View empty;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.bread_crumbs)
BreadCrumbLayout breadCrumbs;
@BindView(R.id.appbar)
AppBarLayout appbar;
@BindView(R.id.recycler_view)
FastScrollRecyclerView recyclerView;
private SongFileAdapter adapter;
private MaterialCab cab;
public FoldersFragment() {
}
public static FoldersFragment newInstance(Context context) {
return newInstance(PreferenceUtil.getInstance(context).getStartDirectory());
}
public static FoldersFragment newInstance(File directory) {
FoldersFragment frag = new FoldersFragment();
Bundle b = new Bundle();
b.putSerializable(PATH, directory);
frag.setArguments(b);
return frag;
}
public void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) {
if (crumb == null) return;
saveScrollPosition();
breadCrumbs.setActiveOrAdd(crumb, false);
if (addToHistory) {
breadCrumbs.addHistory(crumb);
}
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
private void saveScrollPosition() {
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
crumb.setScrollPosition(((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition());
}
}
@Nullable
private BreadCrumbLayout.Crumb getActiveCrumb() {
return breadCrumbs != null && breadCrumbs.size() > 0 ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) : null;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState == null) {
setCrumb(new BreadCrumbLayout.Crumb(FileUtil.safeGetCanonicalFile((File) getArguments().getSerializable(PATH))), true);
} else {
breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS));
getLoaderManager().initLoader(LOADER_ID, null, this);
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_folder, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
getMainActivity().setStatusbarColorAuto();
getMainActivity().setNavigationbarColorAuto();
getMainActivity().setTaskDescriptionColorAuto();
setUpAppbarColor();
setUpToolbar();
setUpBreadCrumbs();
setUpRecyclerView();
setUpAdapter();
}
private void setUpAppbarColor() {
int primaryColor = ThemeStore.primaryColor(getActivity());
appbar.setBackgroundColor(primaryColor);
toolbar.setBackgroundColor(primaryColor);
breadCrumbs.setBackgroundColor(primaryColor);
breadCrumbs.setActivatedContentColor(ToolbarContentTintHelper.toolbarTitleColor(getActivity(), primaryColor));
breadCrumbs.setDeactivatedContentColor(ToolbarContentTintHelper.toolbarSubtitleColor(getActivity(), primaryColor));
}
private void setUpToolbar() {
toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp);
getActivity().setTitle(R.string.app_name);
getMainActivity().setSupportActionBar(toolbar);
}
private void setUpBreadCrumbs() {
breadCrumbs.setCallback(this);
}
private void setUpRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), recyclerView, ThemeStore.accentColor(getActivity()));
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
appbar.addOnOffsetChangedListener(this);
}
private void setUpAdapter() {
adapter = new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
}
});
recyclerView.setAdapter(adapter);
checkIsEmpty();
}
@Override
public void onPause() {
super.onPause();
saveScrollPosition();
}
@Override
public void onDestroyView() {
appbar.removeOnOffsetChangedListener(this);
unbinder.unbind();
super.onDestroyView();
}
@Override
public boolean handleBackPress() {
if (cab != null && cab.isActive()) {
cab.finish();
return true;
}
if (breadCrumbs.popHistory()) {
setCrumb(breadCrumbs.lastHistory(), false);
return true;
}
return false;
}
@NonNull
@Override
public MaterialCab openCab(int menuRes, MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(getMainActivity(), R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(PhonographColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(getActivity())))
.start(callback);
return cab;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_folders, menu);
ToolbarContentTintHelper.handleOnCreateOptionsMenu(getActivity(), toolbar, menu, ATHToolbarActivity.getToolbarBackgroundColor(toolbar));
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(getActivity(), toolbar);
}
public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() ||
FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) ||
FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton()));
@Override
public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) {
setCrumb(crumb, true);
}
public static File getDefaultStartDirectory() {
File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
File startFolder;
if (musicDir.exists() && musicDir.isDirectory()) {
startFolder = musicDir;
} else {
File externalStorage = Environment.getExternalStorageDirectory();
if (externalStorage.exists() && externalStorage.isDirectory()) {
startFolder = externalStorage;
} else {
startFolder = new File("/"); // root
}
}
return startFolder;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_go_to_start_directory:
setCrumb(new BreadCrumbLayout.Crumb(FileUtil.safeGetCanonicalFile(PreferenceUtil.getInstance(getActivity()).getStartDirectory())), true);
return true;
case R.id.action_scan:
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null) {
new ArrayListPathsAsyncTask(getActivity(), this::scanPaths).execute(new ArrayListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER));
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onFileSelected(File file) {
final File canonicalFile = FileUtil.safeGetCanonicalFile(file); // important as we compare the path value later
if (canonicalFile.isDirectory()) {
setCrumb(new BreadCrumbLayout.Crumb(canonicalFile), true);
} else {
FileFilter fileFilter = pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname);
new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> {
int startIndex = -1;
for (int i = 0; i < songs.size(); i++) {
if (canonicalFile.getPath().equals(songs.get(i).data)) {
startIndex = i;
break;
}
}
if (startIndex > -1) {
MusicPlayerRemote.openQueue(songs, startIndex, true);
} else {
Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), canonicalFile.getName())), Snackbar.LENGTH_LONG)
.setAction(R.string.action_scan, v -> scanPaths(new String[]{canonicalFile.getPath()}))
.setActionTextColor(ThemeStore.accentColor(getActivity()))
.show();
}
}).execute(new ListSongsAsyncTask.LoadingInfo(toList(canonicalFile.getParentFile()), fileFilter, getFileComparator()));
}
}
@Override
public void onMultipleItemAction(MenuItem item, List<File> files) {
final int itemId = item.getItemId();
new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> {
if (!songs.isEmpty()) {
SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId);
}
if (songs.size() != files.size()) {
Snackbar.make(coordinatorLayout, R.string.some_files_are_not_listed_in_the_media_store, Snackbar.LENGTH_LONG)
.setAction(R.string.action_scan, v -> {
String[] paths = new String[files.size()];
for (int i = 0; i < files.size(); i++) {
paths[i] = FileUtil.safeGetCanonicalPath(files.get(i));
}
scanPaths(paths);
})
.setActionTextColor(ThemeStore.accentColor(getActivity()))
.show();
}
}).execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator()));
}
private List<File> toList(File file) {
List<File> files = new ArrayList<>(1);
files.add(file);
return files;
}
Comparator<File> fileComparator = (lhs, rhs) -> {
if (lhs.isDirectory() && !rhs.isDirectory()) {
return -1;
} else if (!lhs.isDirectory() && rhs.isDirectory()) {
return 1;
} else {
return lhs.getName().compareToIgnoreCase
(rhs.getName());
}
};
private Comparator<File> getFileComparator() {
return fileComparator;
}
@Override
public void onFileMenuClicked(final File file, View view) {
PopupMenu popupMenu = new PopupMenu(getActivity(), view);
if (file.isDirectory()) {
popupMenu.inflate(R.menu.menu_item_directory);
popupMenu.setOnMenuItemClickListener(item -> {
final int itemId = item.getItemId();
switch (itemId) {
case R.id.action_play_next:
case R.id.action_add_to_current_playing:
case R.id.action_add_to_playlist:
case R.id.action_delete_from_device:
new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> {
if (!songs.isEmpty()) {
SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId);
}
}).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator()));
return true;
case R.id.action_set_as_start_directory:
PreferenceUtil.getInstance(getActivity()).setStartDirectory(file);
Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show();
return true;
case R.id.action_scan:
new ArrayListPathsAsyncTask(getActivity(), this::scanPaths).execute(new ArrayListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER));
return true;
}
return false;
});
} else {
popupMenu.inflate(R.menu.menu_item_file);
popupMenu.setOnMenuItemClickListener(item -> {
final int itemId = item.getItemId();
switch (itemId) {
case R.id.action_play_next:
case R.id.action_add_to_current_playing:
case R.id.action_add_to_playlist:
case R.id.action_go_to_album:
case R.id.action_go_to_artist:
case R.id.action_share:
case R.id.action_details:
case R.id.action_set_as_ringtone:
case R.id.action_delete_from_device:
new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> {
if (!songs.isEmpty()) {
SongMenuHelper.handleMenuClick(getActivity(), songs.get(0), itemId);
} else {
Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), file.getName())), Snackbar.LENGTH_LONG)
.setAction(R.string.action_scan, v -> scanPaths(new String[]{FileUtil.safeGetCanonicalPath(file)}))
.setActionTextColor(ThemeStore.accentColor(getActivity()))
.show();
}
}).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator()));
return true;
case R.id.action_scan:
scanPaths(new String[]{FileUtil.safeGetCanonicalPath(file)});
return true;
}
return false;
});
}
popupMenu.show();
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), container.getPaddingRight(), appbar.getTotalScrollRange() + verticalOffset);
}
private void checkIsEmpty() {
if (empty != null) {
empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
}
private void scanPaths(@Nullable String[] toBeScanned) {
if (getActivity() == null) return;
if (toBeScanned == null || toBeScanned.length < 1) {
Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show();
} else {
MediaScannerConnection.scanFile(getActivity().getApplicationContext(), toBeScanned, null, new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned));
}
}
private void updateAdapter(@NonNull List<File> files) {
adapter.swapDataSet(files);
BreadCrumbLayout.Crumb crumb = getActiveCrumb();
if (crumb != null && recyclerView != null) {
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(crumb.getScrollPosition(), 0);
}
}
@Override
public Loader<List<File>> onCreateLoader(int id, Bundle args) {
return new AsyncFileLoader(this);
}
@Override
public void onLoadFinished(@NonNull Loader<List<File>> loader, List<File> data) {
updateAdapter(data);
}
@Override
public void onLoaderReset(@NonNull Loader<List<File>> loader) {
updateAdapter(new LinkedList<>());
}
private static class AsyncFileLoader extends WrappedAsyncTaskLoader<List<File>> {
private WeakReference<FoldersFragment> fragmentWeakReference;
public AsyncFileLoader(FoldersFragment foldersFragment) {
super(foldersFragment.getActivity());
fragmentWeakReference = new WeakReference<>(foldersFragment);
}
@Override
public List<File> loadInBackground() {
FoldersFragment foldersFragment = fragmentWeakReference.get();
File directory = null;
if (foldersFragment != null) {
BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb();
if (crumb != null) {
directory = crumb.getFile();
}
}
if (directory != null) {
List<File> files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER);
Collections.sort(files, foldersFragment.getFileComparator());
return files;
} else {
return new LinkedList<>();
}
}
}
private static class ListSongsAsyncTask extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private WeakReference<Context> contextWeakReference;
private WeakReference<OnSongsListedCallback> callbackWeakReference;
private final Object extra;
public ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) {
super(context, 500);
this.extra = extra;
contextWeakReference = new WeakReference<>(context);
callbackWeakReference = new WeakReference<>(callback);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
checkCallbackReference();
checkContextReference();
}
@Override
protected List<Song> doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter);
if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null)
return null;
Collections.sort(files, info.fileComparator);
Context context = checkContextReference();
if (isCancelled() || context == null || checkCallbackReference() == null)
return null;
return FileUtil.matchFilesWithMediaStore(context, files);
} catch (Exception e) {
e.printStackTrace();
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(List<Song> songs) {
super.onPostExecute(songs);
OnSongsListedCallback callback = checkCallbackReference();
if (songs != null && callback != null)
callback.onSongsListed(songs, extra);
}
private Context checkContextReference() {
Context context = contextWeakReference.get();
if (context == null) {
cancel(false);
}
return context;
}
private OnSongsListedCallback checkCallbackReference() {
OnSongsListedCallback callback = callbackWeakReference.get();
if (callback == null) {
cancel(false);
}
return callback;
}
public static class LoadingInfo {
public final Comparator<File> fileComparator;
public final FileFilter fileFilter;
public final List<File> files;
public LoadingInfo(@NonNull List<File> files, @NonNull FileFilter fileFilter, @NonNull Comparator<File> fileComparator) {
this.fileComparator = fileComparator;
this.fileFilter = fileFilter;
this.files = files;
}
}
public interface OnSongsListedCallback {
void onSongsListed(@NonNull List<Song> songs, Object extra);
}
}
public static class ArrayListPathsAsyncTask extends ListingFilesDialogAsyncTask<ArrayListPathsAsyncTask.LoadingInfo, String, String[]> {
private WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
public ArrayListPathsAsyncTask(Context context, OnPathsListedCallback callback) {
super(context, 500);
onPathsListedCallbackWeakReference = new WeakReference<>(callback);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
checkCallbackReference();
}
@Override
protected String[] doInBackground(LoadingInfo... params) {
try {
if (isCancelled() || checkCallbackReference() == null) return null;
LoadingInfo info = params[0];
final String[] paths;
if (info.file.isDirectory()) {
List<File> files = FileUtil.listFilesDeep(info.file, info.fileFilter);
if (isCancelled() || checkCallbackReference() == null) return null;
paths = new String[files.size()];
for (int i = 0; i < files.size(); i++) {
File f = files.get(i);
paths[i] = FileUtil.safeGetCanonicalPath(f);
if (isCancelled() || checkCallbackReference() == null) return null;
}
} else {
paths = new String[1];
paths[0] = FileUtil.safeGetCanonicalPath(info.file);
}
return paths;
} catch (Exception e) {
e.printStackTrace();
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(String[] paths) {
super.onPostExecute(paths);
OnPathsListedCallback callback = checkCallbackReference();
if (callback != null && paths != null) {
callback.onPathsListed(paths);
}
}
private OnPathsListedCallback checkCallbackReference() {
OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get();
if (callback == null) {
cancel(false);
}
return callback;
}
public static class LoadingInfo {
public final File file;
public final FileFilter fileFilter;
public LoadingInfo(File file, FileFilter fileFilter) {
this.file = file;
this.fileFilter = fileFilter;
}
}
public interface OnPathsListedCallback {
void onPathsListed(@NonNull String[] paths);
}
}
private static abstract class ListingFilesDialogAsyncTask<Params, Progress, Result> extends DialogAsyncTask<Params, Progress, Result> {
public ListingFilesDialogAsyncTask(Context context) {
super(context);
}
public ListingFilesDialogAsyncTask(Context context, int showDelay) {
super(context, showDelay);
}
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialDialog.Builder(context)
.title(R.string.listing_files)
.progress(true, 0)
.progressIndeterminateStyle(true)
.cancelListener(dialog -> cancel(false))
.dismissListener(dialog -> cancel(false))
.negativeText(android.R.string.cancel)
.onNegative((dialog, which) -> cancel(false))
.show();
}
}
}

View file

@ -16,7 +16,6 @@ import com.google.gson.reflect.TypeToken;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.SortOrder;
import com.kabouzeid.gramophone.model.CategoryInfo;
import com.kabouzeid.gramophone.ui.fragments.mainactivity.folders.FoldersFragment;
import com.kabouzeid.gramophone.ui.fragments.player.NowPlayingScreen;
import java.io.File;
@ -28,7 +27,6 @@ public final class PreferenceUtil {
public static final String GENERAL_THEME = "general_theme";
public static final String REMEMBER_LAST_TAB = "remember_last_tab";
public static final String LAST_PAGE = "last_start_page";
public static final String LAST_MUSIC_CHOOSER = "last_music_chooser";
public static final String NOW_PLAYING_SCREEN_ID = "now_playing_screen_id";
public static final String ARTIST_SORT_ORDER = "artist_sort_order";
@ -76,8 +74,6 @@ public final class PreferenceUtil {
public static final String AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy";
public static final String START_DIRECTORY = "start_directory";
public static final String SYNCHRONIZED_LYRICS_SHOW = "synchronized_lyrics_show";
public static final String INITIALIZED_BLACKLIST = "initialized_blacklist";
@ -161,16 +157,6 @@ public final class PreferenceUtil {
return mPreferences.getInt(LAST_PAGE, 0);
}
public void setLastMusicChooser(final int value) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putInt(LAST_MUSIC_CHOOSER, value);
editor.apply();
}
public final int getLastMusicChooser() {
return mPreferences.getInt(LAST_MUSIC_CHOOSER, 0);
}
public final NowPlayingScreen getNowPlayingScreen() {
int id = mPreferences.getInt(NOW_PLAYING_SCREEN_ID, 0);
for (NowPlayingScreen nowPlayingScreen : NowPlayingScreen.values()) {
@ -454,16 +440,6 @@ public final class PreferenceUtil {
return mPreferences.getString(AUTO_DOWNLOAD_IMAGES_POLICY, "only_wifi");
}
public final File getStartDirectory() {
return new File(mPreferences.getString(START_DIRECTORY, FoldersFragment.getDefaultStartDirectory().getPath()));
}
public void setStartDirectory(File file) {
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(START_DIRECTORY, FileUtil.safeGetCanonicalPath(file));
editor.apply();
}
public final boolean synchronizedLyricsShow() {
return mPreferences.getBoolean(SYNCHRONIZED_LYRICS_SHOW, true);
}

View file

@ -1,416 +0,0 @@
package com.kabouzeid.gramophone.views;
import android.content.Context;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.gramophone.R;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid)
*/
public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener {
@ColorInt
private int contentColorActivated;
@ColorInt
private int contentColorDeactivated;
public static class Crumb implements Parcelable {
public Crumb(File file) {
this.file = file;
}
private final File file;
private int scrollPos;
public int getScrollPosition() {
return scrollPos;
}
public void setScrollPosition(int scrollY) {
this.scrollPos = scrollY;
}
public String getTitle() {
return file.getPath().equals("/") ? "root" : file.getName();
}
public File getFile() {
return file;
}
@Override
public boolean equals(Object o) {
return (o instanceof Crumb) && ((Crumb) o).getFile() != null &&
((Crumb) o).getFile().equals(getFile());
}
@Override
public String toString() {
return "Crumb{" +
"file=" + file +
", scrollPos=" + scrollPos +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(this.file);
dest.writeInt(this.scrollPos);
}
protected Crumb(Parcel in) {
this.file = (File) in.readSerializable();
this.scrollPos = in.readInt();
}
public static final Creator<Crumb> CREATOR = new Creator<Crumb>() {
@Override
public Crumb createFromParcel(Parcel source) {
return new Crumb(source);
}
@Override
public Crumb[] newArray(int size) {
return new Crumb[size];
}
};
}
public interface SelectionCallback {
void onCrumbSelection(Crumb crumb, int index);
}
public BreadCrumbLayout(Context context) {
super(context);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
// Stores currently visible crumbs
private List<Crumb> mCrumbs;
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
private List<Crumb> mOldCrumbs;
// Stores user's navigation history, like a fragment back stack
private List<Crumb> mHistory;
private LinearLayout mChildFrame;
private int mActive;
private SelectionCallback mCallback;
private void init() {
contentColorActivated = ThemeStore.textColorPrimary(getContext());
contentColorDeactivated = ThemeStore.textColorSecondary(getContext());
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
setClipToPadding(false);
setHorizontalScrollBarEnabled(false);
mCrumbs = new ArrayList<>();
mHistory = new ArrayList<>();
mChildFrame = new LinearLayout(getContext());
addView(mChildFrame, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
public void addHistory(Crumb crumb) {
mHistory.add(crumb);
}
public Crumb lastHistory() {
if (mHistory.size() == 0) return null;
return mHistory.get(mHistory.size() - 1);
}
public boolean popHistory() {
if (mHistory.size() == 0) return false;
mHistory.remove(mHistory.size() - 1);
return mHistory.size() != 0;
}
public int historySize() {
return mHistory.size();
}
public void clearHistory() {
mHistory.clear();
}
public void reverseHistory() {
Collections.reverse(mHistory);
}
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
LinearLayout view = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false);
view.setTag(mCrumbs.size());
view.setOnClickListener(this);
ImageView iv = (ImageView) view.getChildAt(1);
if (Build.VERSION.SDK_INT >= 19 && iv.getDrawable() != null) {
iv.getDrawable().setAutoMirrored(true);
}
iv.setVisibility(View.GONE);
mChildFrame.addView(view, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mCrumbs.add(crumb);
if (refreshLayout) {
mActive = mCrumbs.size() - 1;
requestLayout();
}
invalidateActivatedAll();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//RTL works fine like this
View child = mChildFrame.getChildAt(mActive);
if (child != null)
smoothScrollTo(child.getLeft(), 0);
}
public Crumb findCrumb(@NonNull File forDir) {
for (int i = 0; i < mCrumbs.size(); i++) {
if (mCrumbs.get(i).getFile().equals(forDir))
return mCrumbs.get(i);
}
return null;
}
public void clearCrumbs() {
try {
mOldCrumbs = new ArrayList<>(mCrumbs);
mCrumbs.clear();
mChildFrame.removeAllViews();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public Crumb getCrumb(int index) {
return mCrumbs.get(index);
}
public void setCallback(SelectionCallback callback) {
mCallback = callback;
}
private boolean setActive(Crumb newActive) {
mActive = mCrumbs.indexOf(newActive);
invalidateActivatedAll();
boolean success = mActive > -1;
if (success)
requestLayout();
return success;
}
void invalidateActivatedAll() {
for (int i = 0; i < mCrumbs.size(); i++) {
Crumb crumb = mCrumbs.get(i);
invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false, i < mCrumbs.size() - 1).setText(crumb.getTitle());
}
}
void removeCrumbAt(int index) {
mCrumbs.remove(index);
mChildFrame.removeViewAt(index);
}
public boolean trim(String path, boolean dir) {
if (!dir) return false;
int index = -1;
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
File fi = mCrumbs.get(i).getFile();
if (fi.getPath().equals(path)) {
index = i;
break;
}
}
boolean removedActive = index >= mActive;
if (index > -1) {
while (index <= mCrumbs.size() - 1)
removeCrumbAt(index);
if (mChildFrame.getChildCount() > 0) {
int lastIndex = mCrumbs.size() - 1;
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
}
}
return removedActive || mCrumbs.size() == 0;
}
public boolean trim(File file) {
return trim(file.getPath(), file.isDirectory());
}
void updateIndices() {
for (int i = 0; i < mChildFrame.getChildCount(); i++)
mChildFrame.getChildAt(i).setTag(i);
}
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
if (forceRecreate || !setActive(crumb)) {
clearCrumbs();
final List<File> newPathSet = new ArrayList<>();
newPathSet.add(0, crumb.getFile());
File p = crumb.getFile();
while ((p = p.getParentFile()) != null) {
newPathSet.add(0, p);
}
for (int index = 0; index < newPathSet.size(); index++) {
final File fi = newPathSet.get(index);
crumb = new Crumb(fi);
// Restore scroll positions saved before clearing
if (mOldCrumbs != null) {
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
Crumb old = iterator.next();
if (old.equals(crumb)) {
crumb.setScrollPosition(old.getScrollPosition());
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history
break;
}
}
}
addCrumb(crumb, true);
}
// History no longer needed
mOldCrumbs = null;
}
}
public int size() {
return mCrumbs.size();
}
private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone, final boolean allowArrowVisible) {
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
LinearLayout child = (LinearLayout) view;
TextView tv = (TextView) child.getChildAt(0);
tv.setTextColor(contentColor);
ImageView iv = (ImageView) child.getChildAt(1);
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
if (noArrowIfAlone && getChildCount() == 1)
iv.setVisibility(View.GONE);
else if (allowArrowVisible)
iv.setVisibility(View.VISIBLE);
else
iv.setVisibility(View.GONE);
return tv;
}
public int getActiveIndex() {
return mActive;
}
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
this.contentColorActivated = contentColorActivated;
}
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
this.contentColorDeactivated = contentColorDeactivated;
}
@Override
public void onClick(View v) {
if (mCallback != null) {
int index = (Integer) v.getTag();
mCallback.onCrumbSelection(mCrumbs.get(index), index);
}
}
public static class SavedStateWrapper implements Parcelable {
public final int mActive;
public final List<Crumb> mCrumbs;
public final int mVisibility;
public SavedStateWrapper(BreadCrumbLayout view) {
mActive = view.mActive;
mCrumbs = view.mCrumbs;
mVisibility = view.getVisibility();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mActive);
dest.writeTypedList(mCrumbs);
dest.writeInt(this.mVisibility);
}
protected SavedStateWrapper(Parcel in) {
this.mActive = in.readInt();
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
this.mVisibility = in.readInt();
}
public static final Creator<SavedStateWrapper> CREATOR = new Creator<SavedStateWrapper>() {
public SavedStateWrapper createFromParcel(Parcel source) {
return new SavedStateWrapper(source);
}
public SavedStateWrapper[] newArray(int size) {
return new SavedStateWrapper[size];
}
};
}
public SavedStateWrapper getStateWrapper() {
return new SavedStateWrapper(this);
}
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
if (mSavedState != null) {
mActive = mSavedState.mActive;
for (Crumb c : mSavedState.mCrumbs) {
addCrumb(c, false);
}
requestLayout();
setVisibility(mSavedState.mVisibility);
}
}
}