diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 24126a949..0c682642a 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -293,6 +293,7 @@ limitations under the License. + diff --git a/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java b/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java index 3ed021fb8..47fd14d78 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java +++ b/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java @@ -19,6 +19,7 @@ import static android.content.Intent.ACTION_BOOT_COMPLETED; import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME; import static com.google.android.apps.mytracks.Constants.TAG; +import com.google.android.apps.mytracks.services.RemoveTempFilesService; import com.google.android.apps.mytracks.services.TrackRecordingService; import android.content.BroadcastReceiver; @@ -50,6 +51,9 @@ public class BootReceiver extends BroadcastReceiver { Intent startIntent = new Intent(context, TrackRecordingService.class); startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true); context.startService(startIntent); + + Intent removeTempFilesIntent = new Intent(context, RemoveTempFilesService.class); + context.startService(removeTempFilesIntent); } else { Log.w(TAG, "BootReceiver: unsupported action"); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 90743a591..5e8eeb14e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -24,7 +24,6 @@ import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.content.WaypointsColumns; -import com.google.android.apps.mytracks.io.file.TempFileCleaner; import com.google.android.apps.mytracks.services.ITrackRecordingService; import com.google.android.apps.mytracks.services.ServiceUtils; import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; @@ -256,14 +255,9 @@ public class MyTracks extends TabActivity implements OnTouchListener { @Override protected void onStop() { Log.d(TAG, "MyTracks.onStop"); - dataHub.stop(); - tracker.dispatch(); tracker.stop(); - - // Clean up any temporary track files. - TempFileCleaner.clean(); super.onStop(); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java index c2749fce9..0629f4363 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java @@ -16,8 +16,10 @@ package com.google.android.apps.mytracks; import com.google.android.apps.mytracks.content.TrackDataHub; +import com.google.android.apps.mytracks.services.RemoveTempFilesService; import android.app.Application; +import android.content.Intent; /** * MyTracksApplication for keeping global state. @@ -28,6 +30,11 @@ public class MyTracksApplication extends Application { private TrackDataHub trackDataHub; + @Override + public void onCreate() { + startService(new Intent(this, RemoveTempFilesService.class)); + } + /** * Gets the application's TrackDataHub. * diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java deleted file mode 100644 index 42242fa59..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.file; - -import static com.google.android.apps.mytracks.Constants.TAG; - -import com.google.android.apps.mytracks.util.FileUtils; - -import android.os.Environment; -import android.util.Log; - -import java.io.File; - -/** - * A class to clean up old temporary files. - * @author Sandor Dornbush - */ -public class TempFileCleaner { - - private long currentTimeMillis; - - public static void clean() { - (new TempFileCleaner(System.currentTimeMillis())).cleanImpl(); - } - - // @VisibleForTesting - TempFileCleaner(long time) { - currentTimeMillis = time; - } - - private void cleanImpl() { - if (!Environment.getExternalStorageState().equals( - Environment.MEDIA_MOUNTED)) { - return; // Can't do anything now. - } - cleanTmpDirectory("csv"); - cleanTmpDirectory("gpx"); - cleanTmpDirectory("kml"); - cleanTmpDirectory("tcx"); - } - - private void cleanTmpDirectory(String name) { - FileUtils fileUtils = new FileUtils(); - String dirName = fileUtils.buildExternalDirectoryPath(name, "tmp"); - cleanTmpDirectory(new File(dirName)); - } - - // @VisibleForTesting - int cleanTmpDirectory(File dir) { - if (!dir.exists()) { - return 0; - } - int count = 0; - long oldest = currentTimeMillis - 1000 * 3600; - for (File f : dir.listFiles()) { - if (f.lastModified() < oldest) { - if (!f.delete()) { - Log.w(TAG, "Failed to delete file " + f.getAbsolutePath()); - } - count++; - } - } - return count; - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/RemoveTempFilesService.java b/MyTracks/src/com/google/android/apps/mytracks/services/RemoveTempFilesService.java new file mode 100644 index 000000000..0a36b2e66 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/RemoveTempFilesService.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012 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.services; + +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.util.FileUtils; +import com.google.common.annotations.VisibleForTesting; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.IBinder; +import android.util.Log; + +import java.io.File; + +/** + * A service to remove My Tracks temp files older than one hour on the SD card. + * + * @author Jimmy Shih + */ +public class RemoveTempFilesService extends Service { + + private static final String TAG = RemoveTempFilesService.class.getSimpleName(); + private static final int ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000; + + private RemoveTempFilesAsyncTask removeTempFilesAsyncTask = null; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + // Setup an alarm to repeatedly call this service + Intent alarmIntent = new Intent(this, RemoveTempFilesService.class); + PendingIntent pendingIntent = PendingIntent.getService( + this, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + ONE_HOUR_IN_MILLISECONDS, AlarmManager.INTERVAL_HOUR, + pendingIntent); + + // Invoke the AsyncTask to cleanup the temp files + if (removeTempFilesAsyncTask == null + || removeTempFilesAsyncTask.getStatus().equals(AsyncTask.Status.FINISHED)) { + removeTempFilesAsyncTask = new RemoveTempFilesAsyncTask(); + removeTempFilesAsyncTask.execute((Void[]) null); + } + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private class RemoveTempFilesAsyncTask extends AsyncTask { + @Override + protected Void doInBackground(Void... params) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + // Can't do anything + return null; + } + cleanTempDirectory(TrackFileFormat.GPX.getExtension()); + cleanTempDirectory(TrackFileFormat.KML.getExtension()); + cleanTempDirectory(TrackFileFormat.CSV.getExtension()); + cleanTempDirectory(TrackFileFormat.TCX.getExtension()); + return null; + } + + @Override + protected void onPostExecute(Void result) { + stopSelf(); + } + } + + private void cleanTempDirectory(String name) { + FileUtils fileUtils = new FileUtils(); + String dirName = fileUtils.buildExternalDirectoryPath(name, "tmp"); + File dir = new File(dirName); + cleanTempDirectory(dir); + } + + /** + * Removes temp files in a directory older than one hour. + * + * @param dir the directory + * @return the number of files removed. + */ + @VisibleForTesting + int cleanTempDirectory(File dir) { + if (!dir.exists()) { + return 0; + } + int count = 0; + long oneHourAgo = System.currentTimeMillis() - ONE_HOUR_IN_MILLISECONDS; + for (File f : dir.listFiles()) { + if (f.lastModified() < oneHourAgo) { + if (!f.delete()) { + Log.e(TAG, "Unable to delete file: " + f.getAbsolutePath()); + } else { + count++; + } + } + } + return count; + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java deleted file mode 100644 index cea67b571..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.file; - -import static com.google.android.testing.mocking.AndroidMock.expect; - -import com.google.android.apps.mytracks.io.file.TempFileCleaner; -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); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/RemoveTempFilesServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/RemoveTempFilesServiceTest.java new file mode 100644 index 000000000..43c9cbbcb --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/RemoveTempFilesServiceTest.java @@ -0,0 +1,103 @@ +/* + * 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.services; + +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; + +/** + * Tests {@link RemoveTempFilesService}. + * + * @author Sandor Dornbush + */ +public class RemoveTempFilesServiceTest extends AndroidTestCase { + + private static final String DIR_NAME = "/tmp"; + private static final String FILE_NAME = "foo"; + + private RemoveTempFilesService service; + + @UsesMocks({ File.class, }) + protected void setUp() throws Exception { + service = new RemoveTempFilesService(); + }; + + /** + * Tests when the directory doesn't exists. + */ + public void test_noDir() { + File dir = AndroidMock.createMock(File.class, DIR_NAME); + expect(dir.exists()).andStubReturn(false); + AndroidMock.replay(dir); + + assertEquals(0, service.cleanTempDirectory(dir)); + AndroidMock.verify(dir); + } + + /** + * Tests when the directory is empty. + */ + public void test_emptyDir() { + File dir = AndroidMock.createMock(File.class, DIR_NAME); + expect(dir.exists()).andStubReturn(true); + expect(dir.listFiles()).andStubReturn(new File[0]); + AndroidMock.replay(dir); + + assertEquals(0, service.cleanTempDirectory(dir)); + AndroidMock.verify(dir); + } + + /** + * Tests when there is a new file and it shouldn't get deleted. + */ + public void test_newFile() { + File file = AndroidMock.createMock(File.class, DIR_NAME + FILE_NAME); + expect(file.lastModified()).andStubReturn(System.currentTimeMillis()); + + File dir = AndroidMock.createMock(File.class, DIR_NAME); + expect(dir.exists()).andStubReturn(true); + expect(dir.listFiles()).andStubReturn(new File[] { file }); + AndroidMock.replay(dir, file); + + assertEquals(0, service.cleanTempDirectory(dir)); + AndroidMock.verify(dir, file); + } + + /** + * Tests when there is an old file and it should get deleted. + */ + public void test_oldFile() { + File file = AndroidMock.createMock(File.class, DIR_NAME + FILE_NAME); + // qSet to one hour and 1 millisecond later than the current time + expect(file.lastModified()).andStubReturn(System.currentTimeMillis() - 3600001); + expect(file.delete()).andStubReturn(true); + + File dir = AndroidMock.createMock(File.class, DIR_NAME); + expect(dir.exists()).andStubReturn(true); + expect(dir.listFiles()).andStubReturn(new File[] { file }); + AndroidMock.replay(dir, file); + + assertEquals(1, service.cleanTempDirectory(dir)); + AndroidMock.verify(dir, file); + } +}