Add support to choose the ISO 8601 date format as the default track name.

Note using the "yyyy-MM-dd HH:mm" format so it is readable
for users. However, the colon, ":", is not a valid
FAT32 filename character. So when saving to the SD card,
the filename becomes "yyyy-MM-dd HH_mm".
This commit is contained in:
Jimmy Shih
2012-01-09 12:50:53 -08:00
parent 88c4e0e499
commit 43cf0659fd
10 changed files with 196 additions and 82 deletions
@@ -200,6 +200,18 @@ limitations under the License.
<item>@string/sensor_type_value_zephyr</item> <item>@string/sensor_type_value_zephyr</item>
</string-array> </string-array>
<string-array name="settings_recording_track_name_options">
<item>@string/settings_recording_track_name_date_local_option</item>
<item>@string/settings_recording_track_name_date_iso_8601_option</item>
<item>@string/settings_recording_track_name_number_option</item>
</string-array>
<string-array name="settings_recording_track_name_values">
<item>@string/settings_recording_track_name_date_local_value</item>
<item>@string/settings_recording_track_name_date_iso_8601_value</item>
<item>@string/settings_recording_track_name_number_value</item>
</string-array>
<string-array name="waypoint_types"> <string-array name="waypoint_types">
<item>@string/waypoint_type_atm</item> <item>@string/waypoint_type_atm</item>
<item>@string/waypoint_type_bank</item> <item>@string/waypoint_type_bank</item>
+3
View File
@@ -49,6 +49,9 @@ limitations under the License.
<string name="sensor_type_value_polar">POLAR</string> <string name="sensor_type_value_polar">POLAR</string>
<string name="sensor_type_value_srm_ant_bridge">SRM_ANT_BRIDGE</string> <string name="sensor_type_value_srm_ant_bridge">SRM_ANT_BRIDGE</string>
<string name="sensor_type_value_zephyr">ZEPHYR</string> <string name="sensor_type_value_zephyr">ZEPHYR</string>
<string name="settings_recording_track_name_date_iso_8601_value">DATE_ISO_8601</string>
<string name="settings_recording_track_name_date_local_value">DATE_LOCAL</string>
<string name="settings_recording_track_name_number_value">NUMBER</string>
<string name="track_action_end">com.google.android.apps.mytracks.END_CURRENT_TRACK</string> <string name="track_action_end">com.google.android.apps.mytracks.END_CURRENT_TRACK</string>
<string name="track_action_save">android.intent.action.SAVE</string> <string name="track_action_save">android.intent.action.SAVE</string>
<string name="track_action_start">com.google.android.apps.mytracks.START_NEW_TRACK</string> <string name="track_action_start">com.google.android.apps.mytracks.START_NEW_TRACK</string>
+1 -1
View File
@@ -36,9 +36,9 @@ limitations under the License.
<string name="send_to_fusion_tables_key">sendToFusionTables</string> <string name="send_to_fusion_tables_key">sendToFusionTables</string>
<string name="send_to_maps_key">sendToMaps</string> <string name="send_to_maps_key">sendToMaps</string>
<string name="sensor_type_key">sensorType</string> <string name="sensor_type_key">sensorType</string>
<string name="settings_recording_track_name_key">settingsRecordingTrackName</string>
<string name="share_url_only_key">shareUrlOnly</string> <string name="share_url_only_key">shareUrlOnly</string>
<string name="split_frequency_key">splitFrequency</string> <string name="split_frequency_key">splitFrequency</string>
<string name="timestamp_track_name_key">timestampTrackName</string>
<string name="track_color_mode_dynamic_speed_variation_key">trackColorModeDynamicVariation <string name="track_color_mode_dynamic_speed_variation_key">trackColorModeDynamicVariation
</string> </string>
<string name="track_color_mode_fixed_speed_medium_display_key">trackColorModeFixedMediumDisplay <string name="track_color_mode_fixed_speed_medium_display_key">trackColorModeFixedMediumDisplay
+33 -22
View File
@@ -1677,28 +1677,6 @@ limitations under the License.
Screenshot: settings_recording_top.png"> Screenshot: settings_recording_top.png">
Time/distance after which a new marker will be inserted. Time/distance after which a new marker will be inserted.
</string> </string>
<string name="settings_recording_name"
translation_description="In the 'Recording' settings, the checkbox on whether the track should
be named based on the track's start time or based on a number.
Character limit: ~23 (1 line)
Screenshot: settings_recording_bottom.png">
Use timestamp
</string>
<string name="settings_recording_name_summary_off"
translation_description="In the 'Recording' settings, the summary for the 'Use timestamp'
checkbox when it is unchecked explaining using a number as the default track name.
Screenshot: settings_recording_bottom_unchecked.png">
Use a number as the default track name.
</string>
<string name="settings_recording_name_summary_on"
translation_description="In the 'Recording' settings, the summary for the 'Use timestamp'
checkbox when it is checked explaining using the track's start time as the default track name.
Screenshot: settings_recording_bottom.png">
Use the track\'s start time as the default track name.
</string>
<string name="settings_recording_track_distance" <string name="settings_recording_track_distance"
translation_description="In the 'Recording' settings, the dropdown option to configure the translation_description="In the 'Recording' settings, the dropdown option to configure the
distance between two tracks. distance between two tracks.
@@ -1716,6 +1694,39 @@ limitations under the License.
Maximum distance between two recordings of your position. If two positions are further than Maximum distance between two recordings of your position. If two positions are further than
this, the track will be split. this, the track will be split.
</string> </string>
<string name="settings_recording_track_name"
translation_description="In the 'Recording' settings, the title for the dropdown list to choose
the default track name.
Character limit: ~23 (1 line)">
Track name
</string>
<string name="settings_recording_track_name_date_iso_8601_option"
translation_description="In the 'Recording' settings, the option in the 'Track name' dropdown
list to use the track start time in ISO-8601 date format as the track name.
Character limit: ~21 (1 line)">
Date (ISO 8601)
</string>
<string name="settings_recording_track_name_date_local_option"
translation_description="In the 'Recording' settings, the option in the 'Track name' dropdown
list to use the track start time in local date format as the track name.
Character limit: ~21 (1 line)">
Date (local)
</string>
<string name="settings_recording_track_name_number_option"
translation_description="In the 'Recording' settings, the option in the 'Track name' dropdown
list to use a number as the track name.
Character limit: ~21 (1 line)">
Number
</string>
<string name="settings_recording_track_name_summary"
translation_description="In the 'Recording' settings, the summary for the 'Track name' dropdown
list explaining that it chooses the default track name format.">
Default track name
</string>
<string name="settings_recording_voice" <string name="settings_recording_voice"
translation_description="In the 'Recording' settings, the dropdown option to configure the voice translation_description="In the 'Recording' settings, the dropdown option to configure the voice
announcement frequency. announcement frequency.
+6 -6
View File
@@ -137,12 +137,12 @@ limitations under the License.
android:entryValues="@array/recording_auto_resume_timeout_values" android:entryValues="@array/recording_auto_resume_timeout_values"
android:defaultValue="10" /> android:defaultValue="10" />
<CheckBoxPreference <ListPreference android:key="@string/settings_recording_track_name_key"
android:key="@string/timestamp_track_name_key" android:title="@string/settings_recording_track_name"
android:title="@string/settings_recording_name" android:summary="@string/settings_recording_track_name_summary"
android:summaryOn="@string/settings_recording_name_summary_on" android:entries="@array/settings_recording_track_name_options"
android:summaryOff="@string/settings_recording_name_summary_off" android:entryValues="@array/settings_recording_track_name_values"
android:defaultValue="true" /> android:defaultValue="@string/settings_recording_track_name_date_local_value"/>
<com.google.android.apps.mytracks.AutoCompleteTextPreference <com.google.android.apps.mytracks.AutoCompleteTextPreference
android:key="@string/default_activity_key" android:key="@string/default_activity_key"
@@ -18,43 +18,57 @@ package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import java.text.SimpleDateFormat;
/** /**
* Creates a default track name based on the current default track name policy. * Creates a default track name based on the track name setting.
* *
* @author Matthew Simmons * @author Matthew Simmons
*/ */
class DefaultTrackNameFactory { public class DefaultTrackNameFactory {
@VisibleForTesting
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
private final Context context; private final Context context;
DefaultTrackNameFactory(Context context) { public DefaultTrackNameFactory(Context context) {
this.context = context; this.context = context;
} }
/** /**
* Creates a new track name. * Gets the default track name.
* *
* @param trackId The ID for the current track. * @param trackId the track id
* @param startTime The start time, in milliseconds since the epoch, of the * @param startTime the track start time
* current track.
* @return The new track name.
*/ */
String newTrackName(long trackId, long startTime) { public String getDefaultTrackName(long trackId, long startTime) {
if (useTimestampTrackName()) { String trackNameSetting = getTrackNameSetting();
if (trackNameSetting.equals(
context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime); return StringUtils.formatDateTime(context, startTime);
} else if (trackNameSetting.equals(
context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_FORMAT);
return dateFormat.format(startTime);
} else { } else {
return String.format(context.getString(R.string.track_name_format), trackId); return String.format(context.getString(R.string.track_name_format), trackId);
} }
} }
/** Determines whether the preferences allow a timestamp-based track name */ /**
protected boolean useTimestampTrackName() { * Gets the track name setting from the shared preferences.
SharedPreferences prefs = context.getSharedPreferences( */
@VisibleForTesting
String getTrackNameSetting() {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE); Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean( return sharedPreferences.getString(
context.getString(R.string.timestamp_track_name_key), true); context.getString(R.string.settings_recording_track_name_key),
context.getString(R.string.settings_recording_track_name_date_local_value));
} }
} }
@@ -586,7 +586,7 @@ public class TrackRecordingService extends Service {
Uri trackUri = providerUtils.insertTrack(track); Uri trackUri = providerUtils.insertTrack(track);
recordingTrackId = Long.parseLong(trackUri.getLastPathSegment()); recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
track.setId(recordingTrackId); track.setId(recordingTrackId);
track.setName(new DefaultTrackNameFactory(this).newTrackName( track.setName(new DefaultTrackNameFactory(this).getDefaultTrackName(
recordingTrackId, startTime)); recordingTrackId, startTime));
track.setCategory(getDefaultActivityType(this)); track.setCategory(getDefaultActivityType(this));
isRecording = true; isRecording = true;
@@ -32,8 +32,8 @@ import java.util.TimeZone;
public class FileUtils { public class FileUtils {
/** /**
* The maximum Fat32 path length. See * The maximum FAT32 path length. See the FAT32 spec at
* http://www.scribd.com/doc/2187273/FAT32-File-System-Specification * http://msdn.microsoft.com/en-us/windows/hardware/gg463080
*/ */
@VisibleForTesting @VisibleForTesting
static final int MAX_FAT32_PATH_LENGTH = 260; static final int MAX_FAT32_PATH_LENGTH = 260;
@@ -148,7 +148,7 @@ public class FileUtils {
for (int i = 0; i < name.length(); i++) { for (int i = 0; i < name.length(); i++) {
int codePoint = name.codePointAt(i); int codePoint = name.codePointAt(i);
char character = name.charAt(i); char character = name.charAt(i);
if (Character.isLetterOrDigit(character) || codePoint > 127 || character == '-') { if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) {
buffer.appendCodePoint(codePoint); buffer.appendCodePoint(codePoint);
} else { } else {
buffer.append("_"); buffer.append("_");
@@ -157,6 +157,42 @@ public class FileUtils {
String result = buffer.toString(); String result = buffer.toString();
return result.replaceAll("_+", "_"); return result.replaceAll("_+", "_");
} }
/**
* Returns true if it is a special FAT32 character.
*
* @param character the character
*/
private boolean isSpecialFat32(char character) {
switch (character) {
case '$':
case '%':
case '\'':
case '-':
case '_':
case '@':
case '~':
case '`':
case '!':
case '(':
case ')':
case '{':
case '}':
case '^':
case '#':
case '&':
case '+':
case ',':
case ';':
case '=':
case '[':
case ']':
case ' ':
return true;
default:
return false;
}
}
/** /**
* Truncates the name if necessary so the filename path length (directory + * Truncates the name if necessary so the filename path length (directory +
@@ -16,49 +16,55 @@
package com.google.android.apps.mytracks.services; package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import java.text.SimpleDateFormat;
import java.util.Date;
/** /**
* Tests {@link DefaultTrackNameFactory} * Tests {@link DefaultTrackNameFactory}
* *
* @author Matthew Simmons * @author Matthew Simmons
*/ */
public class DefaultTrackNameFactoryTest extends AndroidTestCase { public class DefaultTrackNameFactoryTest extends AndroidTestCase {
/**
* A version of the factory which allows us to supply our own answer as to
* whether a timestamp-based track name should be used.
*/
private static class MockDefaultTrackNameFactory
extends DefaultTrackNameFactory {
private final boolean useTimestamp;
MockDefaultTrackNameFactory(Context context, boolean useTimestamp) { private static final int TRACK_ID = 1;
super(context); private static final long START_TIME = 1288213406000L;
this.useTimestamp = useTimestamp;
} public void testDefaultTrackName_date_local() {
DefaultTrackNameFactory defaultTrackNameFactory = new DefaultTrackNameFactory(getContext()) {
@Override @Override
protected boolean useTimestampTrackName() { String getTrackNameSetting() {
return useTimestamp; return getContext().getString(R.string.settings_recording_track_name_date_local_value);
} }
};
assertEquals(StringUtils.formatDateTime(getContext(), START_TIME),
defaultTrackNameFactory.getDefaultTrackName(TRACK_ID, START_TIME));
} }
private static final long TIMESTAMP = 1288213406000L;
public void testTimestampTrackName() { public void testDefaultTrackName_date_iso_8601() {
DefaultTrackNameFactory factory = DefaultTrackNameFactory defaultTrackNameFactory = new DefaultTrackNameFactory(getContext()) {
new MockDefaultTrackNameFactory(getContext(), true); @Override
String getTrackNameSetting() {
return getContext().getString(R.string.settings_recording_track_name_date_iso_8601_value);
}
};
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
DefaultTrackNameFactory.ISO_8601_FORMAT);
assertEquals(simpleDateFormat.format(new Date(START_TIME)),
defaultTrackNameFactory.getDefaultTrackName(TRACK_ID, START_TIME));
}
public void testDefaultTrackName_number() {
DefaultTrackNameFactory defaultTrackNameFactory = new DefaultTrackNameFactory(getContext()) {
@Override
String getTrackNameSetting() {
return getContext().getString(R.string.settings_recording_track_name_number_value);
}
};
assertEquals( assertEquals(
StringUtils.formatDateTime(getContext(), TIMESTAMP), factory.newTrackName(1, TIMESTAMP)); "Track " + TRACK_ID, defaultTrackNameFactory.getDefaultTrackName(TRACK_ID, START_TIME));
}
public void testIncrementingTrackName() {
DefaultTrackNameFactory factory =
new MockDefaultTrackNameFactory(getContext(), false);
assertEquals("Track 1", factory.newTrackName(1, TIMESTAMP));
} }
} }
@@ -62,13 +62,45 @@ public class FileUtilsTest extends TestCase {
assertEquals(expectedName, dirName); assertEquals(expectedName, dirName);
} }
/**
* Tests sanitize filename.
*/
public void testSanitizeFileName() { public void testSanitizeFileName() {
String name = "Swim\10ming-^across: the/ pacific (ocean).您好-привет_"; String name = "Swim\10ming-^across:/the/ pacific (ocean).";
String expected = "Swim_ming-_across_the_pacific_ocean_您好-привет_"; String expected = "Swim_ming-^across_the_ pacific (ocean)_";
assertEquals(expected, fileUtils.sanitizeFileName(name));
}
/**
* Tests characters in other languages, like Chinese and Russian, are allowed.
*/
public void testSanitizeFileName_i18n() {
String name = "您好-привет";
String expected = "您好-привет";
assertEquals(expected, fileUtils.sanitizeFileName(name));
}
/**
* Tests special FAT32 characters are allowed.
*/
public void testSanitizeFileName_special_characters() {
String name = "$%'-_@~`!(){}^#&+,;=[] ";
String expected = "$%'-_@~`!(){}^#&+,;=[] ";
assertEquals(expected, fileUtils.sanitizeFileName(name)); assertEquals(expected, fileUtils.sanitizeFileName(name));
} }
/**
* Testing collapsing multiple underscores characters.
*/
public void testSanitizeFileName_collapse() {
String name = "hello//there";
String expected = "hello_there";
assertEquals(expected, fileUtils.sanitizeFileName(name));
}
public void testTruncateFileName() { public void testTruncateFileName() {
File directory = new File("/dir1/dir2/"); File directory = new File("/dir1/dir2/");
String suffix = ".gpx"; String suffix = ".gpx";