diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java index 7b5765873..4a2c13873 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java @@ -455,7 +455,7 @@ public class ChartActivity extends Activity implements public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MyTracksConstants.MENU_CHART_SETTINGS: - MyTracks.getInstance().showDialogSafely( + MyTracks.getInstance().getDialogManager().showDialogSafely( DialogManager.DIALOG_CHART_SETTINGS); return true; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java index d41d2a1fc..a79df4864 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java @@ -24,6 +24,7 @@ import android.content.DialogInterface; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.view.WindowManager.BadTokenException; /** * A class to handle a dialog related events for My Tracks. @@ -164,4 +165,67 @@ public class DialogManager { public SendToGoogleDialog getSendToGoogleDialog() { return sendToGoogleDialog; } + + /** + * Shows a dialog with the given message. + * Does it on the UI thread. + * + * @param success if true, displays an info icon/title, otherwise an error + * icon/title + * @param message resource string id + */ + public void showMessageDialog(final int message, final boolean success) { + activity.runOnUiThread(new Runnable() { + public void run() { + AlertDialog dialog = null; + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMessage(activity.getString(message)); + builder.setNegativeButton(activity.getString(R.string.ok), null); + builder.setIcon(success ? android.R.drawable.ic_dialog_info : + android.R.drawable.ic_dialog_alert); + builder.setTitle(success ? R.string.success : R.string.error); + dialog = builder.create(); + dialog.show(); + } + }); + } + + /** + * Just like showDialog, but will catch a BadTokenException that sometimes + * (very rarely) gets thrown. This might happen if the user hits the "back" + * button immediately after sending tracks to google. + * + * @param id the dialog id + */ + public void showDialogSafely(final int id) { + activity.runOnUiThread(new Runnable() { + public void run() { + try { + activity.showDialog(id); + } catch (BadTokenException e) { + Log.w(MyTracksConstants.TAG, + "Could not display dialog with id " + id, e); + } catch (IllegalStateException e) { + Log.w(MyTracksConstants.TAG, + "Could not display dialog with id " + id, e); + } + } + }); + } + + /** + * Dismisses the progress dialog if it is showing. Executed on the UI thread. + */ + public void dismissDialogSafely(final int id) { + activity.runOnUiThread(new Runnable() { + public void run() { + try { + activity.dismissDialog(id); + } catch (IllegalArgumentException e) { + // This will be thrown if this dialog was not shown before. + } + } + }); + } + } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 30f4a9882..756afa7ad 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -31,10 +31,10 @@ import com.google.android.apps.mytracks.io.AuthManagerFactory; import com.google.android.apps.mytracks.io.GpxImporter; import com.google.android.apps.mytracks.io.SendToDocs; import com.google.android.apps.mytracks.io.SendToMyMaps; +import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener; import com.google.android.apps.mytracks.io.TempFileCleaner; import com.google.android.apps.mytracks.io.TrackWriter; import com.google.android.apps.mytracks.io.TrackWriterFactory; -import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener; import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.services.ITrackRecordingService; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; @@ -71,7 +71,6 @@ import android.view.View; import android.view.Window; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager.BadTokenException; import android.widget.RelativeLayout; import android.widget.TabHost; import android.widget.Toast; @@ -104,14 +103,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, private ChartActivity chartActivity; - public ChartActivity getChartActivity() { - return chartActivity; - } - - public void setChartActivity(ChartActivity chartActivity) { - this.chartActivity = chartActivity; - } - /* * Authentication. */ @@ -384,7 +375,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, protected void onStop() { Log.d(MyTracksConstants.TAG, "MyTracks.onStop"); // Clean up any temporary track files. - (new TempFileCleaner()).clean(); + TempFileCleaner.clean(); super.onStop(); } @@ -461,17 +452,17 @@ public class MyTracks extends TabActivity implements OnTouchListener, authenticate(results, MyTracksConstants.SEND_TO_GOOGLE, MyMapsConstants.MAPSHOP_SERVICE); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } case MyTracksConstants.GET_LOGIN: { if (resultCode == RESULT_OK && auth != null) { if (!auth.authResult(resultCode, results)) { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } @@ -527,7 +518,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, authenticate(results, MyTracksConstants.AUTHENTICATE_TO_TRIX, "writely"); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } @@ -538,7 +529,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, R.string.progress_message_authenticating_docs); authenticate(results, MyTracksConstants.SEND_TO_DOCS, "wise"); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } @@ -553,7 +544,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, Runnable onCompletion = new Runnable() { public void run() { setProgressValue(100); - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); runOnUiThread(new Runnable() { public void run() { sendToDocsMessage = sender.getStatusMessage(); @@ -566,13 +557,13 @@ public class MyTracks extends TabActivity implements OnTouchListener, sender.setOnCompletion(onCompletion); sender.run(); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } case MyTracksConstants.SEND_TO_GOOGLE_DIALOG: { shareRequested = false; - showDialogSafely(DIALOG_SEND_TO_GOOGLE); + dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE); break; } case MyTracksConstants.SEND_TO_GOOGLE: { @@ -613,7 +604,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS, RESULT_OK, new Intent()); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); runOnUiThread(new Runnable() { public void run() { handleMapsFinish(); @@ -630,7 +621,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, Handler handler = new Handler(handlerThread.getLooper()); handler.post(sender); } else { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); } break; } @@ -660,7 +651,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, shareLinkToMyMap(selectedTrack.getMapId()); } else { shareRequested = true; - showDialogSafely(DIALOG_SEND_TO_GOOGLE); + dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE); } } break; @@ -774,7 +765,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, } private void importGpxFile(final String fileName) { - showDialogSafely(DIALOG_IMPORT_PROGRESS); + dialogManager.showDialogSafely(DIALOG_IMPORT_PROGRESS); Thread t = new Thread() { @Override public void run() { @@ -806,7 +797,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, // select last track from import file setSelectedTrackId(trackIdsImported[trackIdsImported.length - 1]); } else { - showMessageDialog(message, false/* success */); + dialogManager.showMessageDialog(message, false/* success */); } } finally { runOnUiThread(new Runnable() { @@ -820,44 +811,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, t.start(); } - /** - * Just like showDialog, but will catch a BadTokenException that sometimes - * (very rarely) gets thrown. This might happen if the user hits the "back" - * button immediately after sending tracks to google. - * - * @param id the dialog id - */ - public void showDialogSafely(final int id) { - runOnUiThread(new Runnable() { - public void run() { - try { - showDialog(id); - } catch (BadTokenException e) { - Log.w(MyTracksConstants.TAG, - "Could not display dialog with id " + id, e); - } catch (IllegalStateException e) { - Log.w(MyTracksConstants.TAG, - "Could not display dialog with id " + id, e); - } - } - }); - } - - /** - * Dismisses the progress dialog if it is showing. Executed on the UI thread. - */ - public void dismissDialogSafely(final int id) { - runOnUiThread(new Runnable() { - public void run() { - try { - dismissDialog(id); - } catch (IllegalArgumentException e) { - // This will be thrown if this dialog was not shown before. - } - } - }); - } - // ProgressIndicator implementation @Override @@ -1081,7 +1034,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, @Override public void handleAccountSelected(Account account) { if (account == null) { - dismissDialogSafely(DIALOG_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_PROGRESS); return; } doLogin(results, requestCode, service, account); @@ -1178,7 +1131,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, } setProgressValue(0); clearProgressMessage(); - showDialogSafely(DIALOG_PROGRESS); + dialogManager.showDialogSafely(DIALOG_PROGRESS); if (sendToGoogleDialog.getSendToMyMaps()) { if (!sendToGoogleDialog.getCreateNewMap()) { Intent listIntent = new Intent(this, MyMapsList.class); @@ -1257,43 +1210,20 @@ public class MyTracks extends TabActivity implements OnTouchListener, } } - /** - * Shows a dialog with the given message. - * Does it on the UI thread. - * - * @param success if true, displays an info icon/title, otherwise an error - * icon/title - * @param message resource string id - */ - public void showMessageDialog(final int message, final boolean success) { - runOnUiThread(new Runnable() { - public void run() { - AlertDialog dialog = null; - AlertDialog.Builder builder = new AlertDialog.Builder(MyTracks.this); - builder.setMessage(MyTracks.this.getString(message)); - builder.setNegativeButton(MyTracks.this.getString(R.string.ok), null); - builder.setIcon(success ? android.R.drawable.ic_dialog_info : - android.R.drawable.ic_dialog_alert); - builder.setTitle(success ? R.string.success : R.string.error); - dialog = builder.create(); - dialog.show(); - } - }); - } - /** * Saves the track with the given id to the SD card. * * @param trackId The id of the track to be sent */ public void saveTrack(long trackId, TrackFileFormat format) { - showDialogSafely(DIALOG_WRITE_PROGRESS); + dialogManager.showDialogSafely(DIALOG_WRITE_PROGRESS); final TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format); writer.setOnCompletion(new Runnable() { public void run() { - dismissDialogSafely(DIALOG_WRITE_PROGRESS); - showMessageDialog(writer.getErrorMessage(), writer.wasSuccess()); + dialogManager.dismissDialogSafely(DIALOG_WRITE_PROGRESS); + dialogManager.showMessageDialog(writer.getErrorMessage(), + writer.wasSuccess()); } }); writer.writeTrackAsync(); @@ -1307,7 +1237,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, * @param trackId The id of the track to be sent */ public void sendTrack(long trackId, final TrackFileFormat format) { - showDialogSafely(DIALOG_WRITE_PROGRESS); + dialogManager.showDialogSafely(DIALOG_WRITE_PROGRESS); final TrackWriter writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format); @@ -1319,9 +1249,10 @@ public class MyTracks extends TabActivity implements OnTouchListener, writer.setDirectory(dir); writer.setOnCompletion(new Runnable() { public void run() { - dismissDialogSafely(DIALOG_WRITE_PROGRESS); + dialogManager.dismissDialogSafely(DIALOG_WRITE_PROGRESS); if (!writer.wasSuccess()) { - showMessageDialog(writer.getErrorMessage(), writer.wasSuccess()); + dialogManager.showMessageDialog(writer.getErrorMessage(), + writer.wasSuccess()); } else { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_SUBJECT, @@ -1349,7 +1280,8 @@ public class MyTracks extends TabActivity implements OnTouchListener, Toast.makeText(this, getMapsResultMessage(), Toast.LENGTH_LONG).show(); shareLinkToMyMap(sendToMyMapsMapId); } else { - showDialogSafely(DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT); + dialogManager.showDialogSafely( + DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT); } } @@ -1377,6 +1309,18 @@ public class MyTracks extends TabActivity implements OnTouchListener, return accountChooser; } + public ChartActivity getChartActivity() { + return chartActivity; + } + + public void setChartActivity(ChartActivity chartActivity) { + this.chartActivity = chartActivity; + } + + public DialogManager getDialogManager() { + return dialogManager; + } + public String getSendToMyMapsMapId() { return sendToMyMapsMapId; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java b/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java index eb7f9b2ea..21621225d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java @@ -25,10 +25,18 @@ import java.io.File; */ public class TempFileCleaner { - public TempFileCleaner() { + private long currentTimeMillis; + + public static void clean() { + (new TempFileCleaner(System.currentTimeMillis())).cleanImpl(); } - public void clean() { + // @VisibleForTesting + TempFileCleaner(long time) { + currentTimeMillis = time; + } + + private void cleanImpl() { if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { return; // Can't do anything now. @@ -47,17 +55,19 @@ public class TempFileCleaner { + "tmp")); } - private void cleanTmpDirectory(File dir) { + // @VisibleForTesting + int cleanTmpDirectory(File dir) { if (!dir.exists()) { - return; + return 0; } - File[] list = dir.listFiles(); - long now = System.currentTimeMillis(); - long oldest = now - 1000 * 3600; - for (File f : list) { + int count = 0; + long oldest = currentTimeMillis - 1000 * 3600; + for (File f : dir.listFiles()) { if (f.lastModified() < oldest) { f.delete(); + count++; } } + return count; } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java new file mode 100644 index 000000000..25d600cbe --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 Google 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. + */ +package com.google.android.apps.mytracks.io; + +import static com.google.android.testing.mocking.AndroidMock.expect; +import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.UsesMocks; + +import android.test.AndroidTestCase; + +import java.io.File; + +/** + * @author Sandor Dornbush + */ +public class TempFileCleanerTest extends AndroidTestCase { + + @UsesMocks({ + File.class, + }) + + public void test_noDir() { + File dir = AndroidMock.createMock(File.class, "/no_file"); + TempFileCleaner cleaner = new TempFileCleaner(0); + expect(dir.exists()).andStubReturn(false); + AndroidMock.replay(dir); + assertEquals(0, cleaner.cleanTmpDirectory(dir)); + AndroidMock.verify(dir); + } + + public void test_emptyDir() { + File dir = AndroidMock.createMock(File.class, "/no_file"); + TempFileCleaner cleaner = new TempFileCleaner(0); + expect(dir.exists()).andStubReturn(true); + expect(dir.listFiles()).andStubReturn(new File[0]); + AndroidMock.replay(dir); + assertEquals(0, cleaner.cleanTmpDirectory(dir)); + AndroidMock.verify(dir); + } + + public void test_newFile() { + File dir = AndroidMock.createMock(File.class, "/no_file"); + long now = 100000000; + TempFileCleaner cleaner = new TempFileCleaner(now); + expect(dir.exists()).andStubReturn(true); + File file = AndroidMock.createMock(File.class, "/no_file/foo"); + expect(file.lastModified()).andStubReturn(now); + File[] list = { file }; + expect(dir.listFiles()).andStubReturn(list); + + AndroidMock.replay(dir, file); + assertEquals(0, cleaner.cleanTmpDirectory(dir)); + AndroidMock.verify(dir, file); + } + + public void test_oldFile() { + File dir = AndroidMock.createMock(File.class, "/no_file"); + long now = 100000000; + TempFileCleaner cleaner = new TempFileCleaner(now); + expect(dir.exists()).andStubReturn(true); + File file = AndroidMock.createMock(File.class, "/no_file/foo"); + expect(file.lastModified()).andStubReturn(now - 3600001); + expect(file.delete()).andStubReturn(true); + File[] list = { file }; + expect(dir.listFiles()).andStubReturn(list); + + AndroidMock.replay(dir, file); + assertEquals(1, cleaner.cleanTmpDirectory(dir)); + AndroidMock.verify(dir, file); + } +}