Merge pull request #377 from xbao/sdscanner

SD Scanner
This commit is contained in:
Adrian Ulrich 2016-06-12 16:24:48 +02:00 committed by GitHub
commit 6f076891cd
6 changed files with 934 additions and 0 deletions

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2013-2014 Jeremy Erickson
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
-->
<!--
Copied from SD Scanner's layout/main.xml with minor changes
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:paddingBottom="16dp"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="@string/path_label" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal" >
<EditText
android:id="@+id/path_widget"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/path_button"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_revert"
style="?android:attr/borderlessButtonStyle" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="center_vertical"
android:text="@string/db_label" />
<CheckBox
android:id="@+id/restrict_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/restrict_label" />
<Button
android:id="@+id/start_button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_gravity="center"
android:text="@string/button_start">
<requestFocus />
</Button>
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:progress="0" />
<TextView
android:id="@+id/progress_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="10000"
android:text="@string/progress_unstarted_label" />
<TextView
android:id="@+id/debug_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="" />
</LinearLayout>

View File

@ -293,4 +293,24 @@ THE SOFTWARE.
<string name="permission_request_summary">Vanilla Music needs read permission to display your music library</string>
<string name="reverse_sort">Reverse sort</string>
<!-- SD Scanner -->
<string name="button_start">Start Rescan</string>
<string name="database_proc">Examined</string>
<string name="delete_proc">Removed reference to</string>
<string name="db_label">Will also check existing media database for updated or deleted files.</string>
<string name="db_error_failure">Encountered error reading media database, and might miss updated or deleted files or rescan up-to-date files.</string>
<string name="db_error_recovered">Encountered error reading media database, but recovered.</string>
<string name="db_error_retrying">Encountered error reading media database. Retrying in 1 second...</string>
<string name="final_proc">Processed</string>
<string name="path_label">Path to check for new files:</string>
<string name="progress_completed_label">Completed, ready to start another scan.</string>
<string name="progress_error_bad_path_label">Scan failed: bad path specified for new file search.</string>
<string name="progress_filelist_label">Preparing initial list of files...</string>
<string name="progress_database_label">Querying database...</string>
<string name="progress_unstarted_label">Not yet started.</string>
<string name="restrict_label">Ignore updated and deleted files outside of the specified path.</string>
<string name="skipping_folder_label">Encountered an error and skipping</string>
<string name="sdscanner">SD Scanner</string>
</resources>

View File

@ -45,6 +45,9 @@ THE SOFTWARE.
<header
android:fragment="ch.blinkenlights.android.vanilla.PreferencesTheme"
android:title="@string/theme" />
<header
android:fragment="ch.blinkenlights.android.vanilla.SDScannerFragment"
android:title="@string/sdscanner" />
<header
android:fragment="ch.blinkenlights.android.vanilla.PreferencesActivity$AboutFragment"
android:title="@string/about" />

View File

@ -0,0 +1,202 @@
/* SD Scanner - A manual implementation of the SD rescan process, compatible
* with Android 4.4
*
* Copyright (C) 2013-2014 Jeremy Erickson
* Copyright (C) 2016 Xiao Bao Clark
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package ch.blinkenlights.android.vanilla;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.gmail.jerickson314.sdscanner.ScanFragment;
import com.gmail.jerickson314.sdscanner.UIStringGenerator;
import java.io.File;
import java.io.IOException;
/**
* Fragment version of the MainActivity from the SD Scanner app
*/
public class SDScannerFragment extends Fragment
implements ScanFragment.ScanProgressCallbacks
{
ScanFragment mScanFragment;
@Override
public void updateProgressNum(int progressNum) {
ProgressBar progressBar = (ProgressBar)findViewById(R.id.progress_bar);
progressBar.setProgress(progressNum);
}
@Override
public void updateProgressText(UIStringGenerator progressText) {
TextView progressLabel = (TextView)findViewById(R.id.progress_label);
progressLabel.setText(progressText.toString(getActivity()));
}
@Override
public void updateDebugMessages(UIStringGenerator debugMessages) {
TextView debugLabel = (TextView)findViewById(R.id.debug_label);
debugLabel.setText(debugMessages.toString(getActivity()));
}
@Override
public void updatePath(String path) {
EditText pathText = (EditText) findViewById(R.id.path_widget);
pathText.setText(path);
}
@Override
public void updateStartButtonEnabled(boolean startButtonEnabled) {
Button startButton = (Button)findViewById(R.id.start_button);
startButton.setEnabled(startButtonEnabled);
}
public void updateRestrictCheckboxChecked(boolean checked) {
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
restrictCheckbox.setChecked(checked);
}
@Override
public void signalFinished() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.sdscanner_fragment, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Setup with values from fragment.
updateProgressNum(mScanFragment.getProgressNum());
updateProgressText(mScanFragment.getProgressText());
updateDebugMessages(mScanFragment.getDebugMessages());
updateStartButtonEnabled(mScanFragment.getStartButtonEnabled());
// Update path from preferences
SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
try {
updatePath(preferences.getString("path",
Environment.getExternalStorageDirectory().getCanonicalPath()));
updateRestrictCheckboxChecked(preferences.getBoolean(
"restrict_db_scan", false));
}
catch (IOException Ex) {
// Should never happen, but getCanonicalPath() declares the throw.
updatePath("");
updateRestrictCheckboxChecked(false);
}
// Make debug output scrollable.
TextView debugLabel = (TextView)findViewById(R.id.debug_label);
debugLabel.setMovementMethod(new ScrollingMovementMethod());
view.findViewById(R.id.path_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
defaultButtonPressed(v);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
view.findViewById(R.id.start_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
startButtonPressed(v);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
mScanFragment = (ScanFragment) fm.findFragmentByTag("scan");
if (mScanFragment == null) {
mScanFragment = new ScanFragment();
fm.beginTransaction().add(mScanFragment, "scan").commit();
}
mScanFragment.setScanProgressCallbacks(this);
}
@Override
public void onDestroy() {
super.onDestroy();
mScanFragment.setScanProgressCallbacks(null);
}
private View findViewById(int viewId) {
return getView().findViewById(viewId);
}
@Override
public void onStop() {
super.onStop();
// Write setting to preferences
EditText pathText = (EditText) findViewById(R.id.path_widget);
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("path", pathText.getText().toString());
editor.putBoolean("restrict_db_scan", restrictCheckbox.isChecked());
editor.commit();
}
public void defaultButtonPressed(View view) throws IOException {
updatePath(Environment.getExternalStorageDirectory().getCanonicalPath());
}
public void startButtonPressed(View view) throws IOException {
startScan();
}
public void startScan() throws IOException {
EditText pathText = (EditText) findViewById(R.id.path_widget);
File path = new File(pathText.getText().toString());
CheckBox restrictCheckbox = (CheckBox) findViewById(R.id.restrict_checkbox);
mScanFragment.startScan(path.getCanonicalFile(), restrictCheckbox.isChecked());
}
}

View File

@ -0,0 +1,533 @@
/* SD Scanner - A manual implementation of the SD rescan process, compatible
* with Android 4.4.
*
* This file contains the fragment that actually performs all scan activity
* and retains state across configuration changes.
*
* Copyright (C) 2013-2014 Jeremy Erickson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package com.gmail.jerickson314.sdscanner;
import android.app.Activity;
import android.app.Fragment;
import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import ch.blinkenlights.android.vanilla.R;
public class ScanFragment extends Fragment {
private static final String[] MEDIA_PROJECTION =
{MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DATE_MODIFIED};
private static final String[] STAR = {"*"};
private static final int DB_RETRIES = 3;
Context mApplicationContext;
ArrayList<String> mPathNames;
TreeSet<File> mFilesToProcess;
int mLastGoodProcessedIndex;
private Handler mHandler = new Handler();
int mProgressNum;
UIStringGenerator mProgressText =
new UIStringGenerator(R.string.progress_unstarted_label);
UIStringGenerator mDebugMessages = new UIStringGenerator();
boolean mStartButtonEnabled;
boolean mHasStarted = false;
/**
* Callback interface used by the fragment to update the Activity.
*/
public static interface ScanProgressCallbacks {
void updateProgressNum(int progressNum);
void updateProgressText(UIStringGenerator progressText);
void updateDebugMessages(UIStringGenerator debugMessages);
void updatePath(String path);
void updateStartButtonEnabled(boolean startButtonEnabled);
void signalFinished();
}
private ScanProgressCallbacks mCallbacks;
private void updateProgressNum(int progressNum) {
mProgressNum = progressNum;
if (mCallbacks != null) {
mCallbacks.updateProgressNum(mProgressNum);
}
}
private void updateProgressText(int resId) {
updateProgressText(new UIStringGenerator(resId));
}
private void updateProgressText(int resId, String string) {
updateProgressText(new UIStringGenerator(resId, string));
}
private void updateProgressText(UIStringGenerator progressText) {
mProgressText = progressText;
if (mCallbacks != null) {
mCallbacks.updateProgressText(mProgressText);
}
}
private void addDebugMessage(int resId, String string) {
mDebugMessages.addSubGenerator(resId);
mDebugMessages.addSubGenerator(string + "\n");
if (mCallbacks != null) {
mCallbacks.updateDebugMessages(mDebugMessages);
}
}
private void addDebugMessage(String debugMessage) {
mDebugMessages.addSubGenerator(debugMessage + "\n");
if (mCallbacks != null) {
mCallbacks.updateDebugMessages(mDebugMessages);
}
}
private void resetDebugMessages() {
mDebugMessages = new UIStringGenerator();
if (mCallbacks != null) {
mCallbacks.updateDebugMessages(mDebugMessages);
}
}
private void updateStartButtonEnabled(boolean startButtonEnabled) {
mStartButtonEnabled = startButtonEnabled;
if (mCallbacks != null) {
mCallbacks.updateStartButtonEnabled(mStartButtonEnabled);
}
}
private void signalFinished() {
if (mCallbacks != null) {
mCallbacks.signalFinished();
}
}
public int getProgressNum() {
return mProgressNum;
}
public UIStringGenerator getProgressText() {
return mProgressText;
}
public UIStringGenerator getDebugMessages() {
return mDebugMessages;
}
public boolean getStartButtonEnabled() {
return mStartButtonEnabled;
}
public boolean getHasStarted() {
return mHasStarted;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(activity instanceof ScanProgressCallbacks) {
mCallbacks = (ScanProgressCallbacks) activity;
}
mApplicationContext = activity.getApplicationContext();
}
public void setScanProgressCallbacks(ScanProgressCallbacks callbacks) {
mCallbacks = callbacks;
}
public ScanFragment() {
super();
// Set correct initial values.
mProgressNum = 0;
mStartButtonEnabled = true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
}
// Purely for debugging and not normally used, so does not translate
// strings.
public void listPathNamesOnDebug() {
StringBuffer listString = new StringBuffer();
listString.append("\n\nScanning paths:\n");
Iterator<String> iterator = mPathNames.iterator();
while (iterator.hasNext()) {
listString.append(iterator.next() + "\n");
}
addDebugMessage(listString.toString());
}
public void scannerEnded() {
updateProgressNum(0);
updateProgressText(R.string.progress_completed_label);
updateStartButtonEnabled(true);
signalFinished();
}
public void startMediaScanner(){
//listPathNamesOnDebug();
if (mPathNames.size() == 0) {
scannerEnded();
}
else {
MediaScannerConnection.scanFile(
mApplicationContext,
mPathNames.toArray(new String[mPathNames.size()]),
null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
mHandler.post(new Updater(path));
}
});
}
}
public void startScan(File path, boolean restrictDbUpdate) {
mHasStarted = true;
updateStartButtonEnabled(false);
updateProgressText(R.string.progress_filelist_label);
mFilesToProcess = new TreeSet<File>();
resetDebugMessages();
if (path.exists()) {
this.new PreprocessTask().execute(new ScanParameters(path, restrictDbUpdate));
}
else {
updateProgressText(R.string.progress_error_bad_path_label);
updateStartButtonEnabled(true);
signalFinished();
}
}
static class ProgressUpdate {
public enum Type {
DATABASE, STATE, DEBUG
}
Type mType;
public Type getType() {
return mType;
}
int mResId;
public int getResId() {
return mResId;
}
String mString;
public String getString() {
return mString;
}
int mProgress;
public int getProgress() {
return mProgress;
}
public ProgressUpdate(Type type, int resId, String string,
int progress) {
mType = type;
mResId = resId;
mString = string;
mProgress = progress;
}
}
static ProgressUpdate debugUpdate(int resId, String string) {
return new ProgressUpdate(ProgressUpdate.Type.DEBUG, resId, string, 0);
}
static ProgressUpdate debugUpdate(int resId) {
return debugUpdate(resId, "");
}
static ProgressUpdate databaseUpdate(String file, int progress) {
return new ProgressUpdate(ProgressUpdate.Type.DATABASE, 0, file,
progress);
}
static ProgressUpdate stateUpdate(int resId) {
return new ProgressUpdate(ProgressUpdate.Type.STATE, resId, "", 0);
}
static class ScanParameters {
File mPath;
boolean mRestrictDbUpdate;
public ScanParameters(File path, boolean restrictDbUpdate) {
mPath = path;
mRestrictDbUpdate = restrictDbUpdate;
}
public File getPath() {
return mPath;
}
public boolean shouldScan(File file, boolean fromDb)
throws IOException {
// Empty directory check.
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null || files.length == 0) {
Log.w("SDScanner", "Scan of empty directory " +
file.getCanonicalPath() + " skipped to avoid bug.");
return false;
}
}
if (!mRestrictDbUpdate && fromDb) {
return true;
}
while (file != null) {
if (file.equals(mPath)) {
return true;
}
file = file.getParentFile();
}
// If we fell through here, got up to root without encountering the
// path to scan.
if (!fromDb) {
Log.w("SDScanner", "File " + file.getCanonicalPath() +
" outside of scan directory skipped.");
}
return false;
}
}
class PreprocessTask extends AsyncTask<ScanParameters, ProgressUpdate, Void> {
private void recursiveAddFiles(File file, ScanParameters scanParameters)
throws IOException {
if (!scanParameters.shouldScan(file, false)) {
// If we got here, there file was either outside the scan
// directory, or was an empty directory.
return;
}
if (!mFilesToProcess.add(file)) {
// Avoid infinite recursion caused by symlinks.
// If mFilesToProcess already contains this file, add() will
// return false.
return;
}
if (file.isDirectory()) {
boolean nomedia = new File(file, ".nomedia").exists();
// Only recurse downward if not blocked by nomedia.
if (!nomedia) {
File[] files = file.listFiles();
if (files != null) {
for (File nextFile : files) {
recursiveAddFiles(nextFile.getCanonicalFile(),
scanParameters);
}
}
else {
publishProgress(debugUpdate(
R.string.skipping_folder_label,
" " + file.getPath()));
}
}
}
}
protected void dbOneTry(ScanParameters parameters) {
Cursor cursor = mApplicationContext.getContentResolver().query(
MediaStore.Files.getContentUri("external"),
MEDIA_PROJECTION,
//STAR,
null,
null,
null);
int data_column =
cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
int modified_column =
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
int totalSize = cursor.getCount();
int currentItem = 0;
int reportFreq = 0;
// Used to calibrate reporting frequency
long startTime = SystemClock.currentThreadTimeMillis();
while (cursor.moveToNext()) {
currentItem++;
try {
File file = new File(cursor.getString(data_column)).getCanonicalFile();
if ((!file.exists() ||
file.lastModified() / 1000L >
cursor.getLong(modified_column))
&& parameters.shouldScan(file, true)) {
// Media scanner handles these cases.
// Is a set, so OK if already present.
mFilesToProcess.add(file);
}
else {
// Don't want to waste time scanning an up-to-date
// file.
mFilesToProcess.remove(file);
}
if (reportFreq == 0) {
// Calibration phase
if (SystemClock.currentThreadTimeMillis() - startTime > 25) {
reportFreq = currentItem + 1;
}
}
else if (currentItem % reportFreq == 0) {
publishProgress(databaseUpdate(file.getPath(),
(100 * currentItem) / totalSize));
}
}
catch (IOException ex) {
// Just ignore it for now.
}
}
// Don't need the cursor any more.
cursor.close();
}
@Override
protected Void doInBackground(ScanParameters... parameters) {
try {
recursiveAddFiles(parameters[0].getPath(), parameters[0]);
}
catch (IOException Ex) {
// Do nothing.
}
// Parse database
publishProgress(stateUpdate(R.string.progress_database_label));
boolean dbSuccess = false;
int numRetries = 0;
while (!dbSuccess && numRetries < DB_RETRIES) {
dbSuccess = true;
try {
dbOneTry(parameters[0]);
}
catch (Exception Ex) {
// For any of these errors, try again.
numRetries++;
dbSuccess = false;
if (numRetries < DB_RETRIES) {
publishProgress(stateUpdate(
R.string.db_error_retrying));
SystemClock.sleep(1000);
}
}
}
if (numRetries > 0) {
if (dbSuccess) {
publishProgress(debugUpdate(R.string.db_error_recovered));
}
else {
publishProgress(debugUpdate(R.string.db_error_failure));
}
}
// Prepare final path list for processing.
mPathNames = new ArrayList<String>(mFilesToProcess.size());
Iterator<File> iterator = mFilesToProcess.iterator();
while (iterator.hasNext()) {
mPathNames.add(iterator.next().getPath());
}
mLastGoodProcessedIndex = -1;
return null;
}
@Override
protected void onProgressUpdate(ProgressUpdate... progress) {
switch (progress[0].getType()) {
case DATABASE:
updateProgressText(R.string.database_proc,
" " + progress[0].getString());
updateProgressNum(progress[0].getProgress());
break;
case STATE:
updateProgressText(progress[0].getResId());
updateProgressNum(0);
break;
case DEBUG:
addDebugMessage(progress[0].getResId(), progress[0].getString());
}
}
@Override
protected void onPostExecute(Void result) {
startMediaScanner();
}
}
class Updater implements Runnable {
String mPathScanned;
public Updater(String path) {
mPathScanned = path;
}
public void run() {
if (mLastGoodProcessedIndex + 1 < mPathNames.size() &&
mPathNames.get(mLastGoodProcessedIndex
+ 1).equals(mPathScanned)) {
mLastGoodProcessedIndex++;
}
else {
int newIndex = mPathNames.indexOf(mPathScanned);
if (newIndex > -1) {
mLastGoodProcessedIndex = newIndex;
}
}
int progress = (100 * (mLastGoodProcessedIndex + 1))
/ mPathNames.size();
if (progress == 100) {
scannerEnded();
}
else {
updateProgressNum(progress);
updateProgressText(R.string.final_proc, " " + mPathScanned);
}
}
}
}

View File

@ -0,0 +1,87 @@
/* SD Scanner - A manual implementation of the SD rescan process, compatible
* with Android 4.4
*
* Copyright (C) 2013-2014 Jeremy Erickson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package com.gmail.jerickson314.sdscanner;
import android.app.Activity;
import java.util.ArrayList;
import java.util.Iterator;
public class UIStringGenerator {
private static interface SubGenerator {
public String toString(Activity activity);
}
private static class ResourceSubGenerator implements SubGenerator {
int mResId;
public ResourceSubGenerator(int resId) {
mResId = resId;
}
public String toString(Activity activity) {
return activity.getString(mResId);
}
}
private static class StringSubGenerator implements SubGenerator {
String mString;
public StringSubGenerator(String string) {
mString = string;
}
public String toString(Activity activity) {
return mString;
}
}
ArrayList<SubGenerator> mSubGenerators = new ArrayList<SubGenerator>();
public void addSubGenerator(int resId) {
mSubGenerators.add(new ResourceSubGenerator(resId));
}
public void addSubGenerator(String string) {
mSubGenerators.add(new StringSubGenerator(string));
}
public UIStringGenerator(int resId) {
addSubGenerator(resId);
}
public UIStringGenerator(int resId, String string) {
addSubGenerator(resId);
addSubGenerator(string);
}
public UIStringGenerator(String string) {
addSubGenerator(string);
}
public UIStringGenerator() {
// Doing nothing results in empty string.
}
public String toString(Activity activity) {
StringBuilder toReturn = new StringBuilder();
Iterator<SubGenerator> iterator = mSubGenerators.iterator();
while (iterator.hasNext()) {
toReturn.append(iterator.next().toString(activity));
}
return toReturn.toString();
}
}