mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-05 19:13:07 +02:00
Handles interrupt and error cleanup during import
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Utitlites for sending pageviews to Google Analytics.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class AnalyticsUtils {
|
||||
|
||||
public static final String ACTION_EXPORT_ALL_PREFIX = "/action/export_all_";
|
||||
public static final String ACTION_EXPORT_PREFIX = "/action/export_";
|
||||
public static final String ACTION_EXPORT_FUSION_TABLES = "/action/export_fusion_tables";
|
||||
public static final String ACTION_EXPORT_MAPS = "/action/export_maps";
|
||||
public static final String ACTION_EXPORT_SPREADSHEETS = "/action/export_spreadsheets";
|
||||
public static final String ACTION_IMPORT_ALL_PREFIX = "/action/import_all_";
|
||||
public static final String ACTION_INSERT_MARKER = "/action/insert_marker";
|
||||
public static final String ACTION_PAUSE_TRACK = "/action/pause_track";
|
||||
public static final String ACTION_PLAY = "/action/play";
|
||||
public static final String ACTION_RECORD_TRACK = "/action/record_track";
|
||||
public static final String ACTION_RESUME_TRACK = "/action/resume_track";
|
||||
public static final String ACTION_SHARE_DRIVE = "/action/share_drive";
|
||||
public static final String ACTION_STOP_RECORDING = "/action/stop_recording";
|
||||
|
||||
public static final String PAGE_TRACK_DETAIL = "/page/track_detail";
|
||||
public static final String PAGE_TRACK_LIST = "/page/track_list";
|
||||
|
||||
public static final String SENSOR_ANT = "/sensor/ant";
|
||||
public static final String SENSOR_POLAR = "/sensor/polar";
|
||||
public static final String SENSOR_ZEPHYR = "/sensor/zephyr";
|
||||
|
||||
private static final String UA = "UA-7222692-2";
|
||||
private static final String PRODUCT_NAME = "android-mytracks";
|
||||
private static GoogleAnalyticsTracker tracker;
|
||||
|
||||
private AnalyticsUtils() {}
|
||||
|
||||
/**
|
||||
* Sends a page view.
|
||||
*
|
||||
* @param context the context
|
||||
* @param page the page
|
||||
*/
|
||||
public static void sendPageViews(Context context, String page) {
|
||||
if (tracker == null) {
|
||||
tracker = GoogleAnalyticsTracker.getInstance();
|
||||
tracker.startNewSession(UA, context);
|
||||
tracker.setProductVersion(PRODUCT_NAME, SystemUtils.getMyTracksVersion(context));
|
||||
}
|
||||
tracker.trackPageView(page);
|
||||
}
|
||||
|
||||
public static void dispatch() {
|
||||
if (tracker != null) {
|
||||
tracker.dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* API level 10 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@TargetApi(10)
|
||||
public class Api10Adapter extends Api9Adapter {
|
||||
|
||||
@Override
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
|
||||
try {
|
||||
return bluetoothDevice.createInsecureRfcommSocketToServiceRecord(
|
||||
BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
} catch (IOException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.ContextualActionModeCallback;
|
||||
import com.google.android.apps.mytracks.TrackController;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API level 11 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public class Api11Adapter extends Api10Adapter {
|
||||
|
||||
@Override
|
||||
public void hideTitle(Activity activity) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureActionBarHomeAsUp(Activity activity) {
|
||||
ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureListViewContextualMenu(final Activity activity, final ListView listView,
|
||||
final ContextualActionModeCallback contextualActionModeCallback) {
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mode.getMenuInflater().inflate(R.menu.list_context_menu, menu);
|
||||
setActionModeTitle(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
contextualActionModeCallback.onPrepare(
|
||||
menu, getCheckedPositions(listView), listView.getCheckedItemIds(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(
|
||||
ActionMode mode, int position, long id, boolean checked) {
|
||||
setActionModeTitle(mode);
|
||||
mode.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
if (contextualActionModeCallback.onClick(
|
||||
item.getItemId(), getCheckedPositions(listView), listView.getCheckedItemIds())) {
|
||||
mode.finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action mode title
|
||||
*
|
||||
* @param mode action mode
|
||||
*/
|
||||
private void setActionModeTitle(ActionMode mode) {
|
||||
int count = listView.getCheckedItemCount();
|
||||
mode.setTitle(activity.getString(R.string.list_item_selected, count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the checked positions in a list view.
|
||||
*
|
||||
* @param list the list view
|
||||
*/
|
||||
private int[] getCheckedPositions(ListView list) {
|
||||
SparseBooleanArray positions = list.getCheckedItemPositions();
|
||||
ArrayList<Integer> arrayList = new ArrayList<Integer>();
|
||||
for (int i = 0; i < positions.size(); i++) {
|
||||
int key = positions.keyAt(i);
|
||||
if (positions.valueAt(i)) {
|
||||
arrayList.add(key);
|
||||
}
|
||||
}
|
||||
int[] result = new int[arrayList.size()];
|
||||
for (int i = 0; i < arrayList.size(); i++) {
|
||||
result[i] = arrayList.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureSearchWidget(
|
||||
Activity activity, MenuItem menuItem, final TrackController trackController) {
|
||||
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||
SearchView searchView = (SearchView) menuItem.getActionView();
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
|
||||
searchView.setQueryRefinementEnabled(true);
|
||||
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
// Hide and show trackController when search widget has focus/no focus
|
||||
if (trackController != null) {
|
||||
if (hasFocus) {
|
||||
trackController.hide();
|
||||
} else {
|
||||
trackController.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleSearchMenuSelection(Activity activity) {
|
||||
// Returns false to allow the platform to expand the search widget.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) {
|
||||
arrayAdapter.addAll(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidMenu(Activity activity) {
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpinnerBackgroundLight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabBackground(TabWidget tabWidget) {
|
||||
for (int i = 0; i < tabWidget.getChildCount(); i++) {
|
||||
tabWidget.getChildAt(i).setBackgroundResource(R.drawable.tab_indicator_mytracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.TrackController;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.SearchView;
|
||||
|
||||
/**
|
||||
* API level 14 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@TargetApi(14)
|
||||
public class Api14Adapter extends Api11Adapter {
|
||||
|
||||
@Override
|
||||
public void configureActionBarHomeAsUp(Activity activity) {
|
||||
ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureSearchWidget(
|
||||
Activity activity, final MenuItem menuItem, TrackController trackController) {
|
||||
super.configureSearchWidget(activity, menuItem, trackController);
|
||||
SearchView searchView = (SearchView) menuItem.getActionView();
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
menuItem.collapseActionView();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
menuItem.collapseActionView();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleSearchKey(MenuItem menuItem) {
|
||||
menuItem.expandActionView();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGoogleFeedbackAvailable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.widgets.TrackWidgetProvider;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
|
||||
/**
|
||||
* API level 16 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@TargetApi(16)
|
||||
public class Api16Adapter extends Api14Adapter {
|
||||
|
||||
private static final String APP_WIDGET_SIZE_KEY = "app_widget_size_key";
|
||||
|
||||
@Override
|
||||
public int getAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId) {
|
||||
Bundle bundle = appWidgetManager.getAppWidgetOptions(appWidgetId);
|
||||
return bundle.getInt(APP_WIDGET_SIZE_KEY, getAppWidgetSizeDefault(bundle));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected int getAppWidgetSizeDefault(Bundle bundle) {
|
||||
return TrackWidgetProvider.HOME_SCREEN_DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId, int size) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(APP_WIDGET_SIZE_KEY, size);
|
||||
appWidgetManager.updateAppWidgetOptions(appWidgetId, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalLayoutListener(
|
||||
ViewTreeObserver observer, OnGlobalLayoutListener listener) {
|
||||
observer.removeOnGlobalLayoutListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.widgets.TrackWidgetProvider;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* API level 17 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@TargetApi(17)
|
||||
public class Api17Adapter extends Api16Adapter {
|
||||
|
||||
@Override
|
||||
protected int getAppWidgetSizeDefault(Bundle bundle) {
|
||||
boolean isKeyguard = bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1)
|
||||
== AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
|
||||
return isKeyguard ? TrackWidgetProvider.KEYGUARD_DEFAULT_SIZE
|
||||
: TrackWidgetProvider.HOME_SCREEN_DEFAULT_SIZE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.ContextualActionModeCallback;
|
||||
import com.google.android.apps.mytracks.TrackController;
|
||||
import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager;
|
||||
import com.google.android.apps.mytracks.widgets.TrackWidgetProvider;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.apache.ApacheHttpTransport;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API level 8 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class Api8Adapter implements ApiAdapter {
|
||||
|
||||
@Override
|
||||
public void applyPreferenceChanges(Editor editor) {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableStrictMode() {
|
||||
// Not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] copyByteArray(byte[] input, int start, int end) {
|
||||
int length = end - start;
|
||||
byte[] output = new byte[length];
|
||||
System.arraycopy(input, start, output, 0, length);
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpTransport getHttpTransport() {
|
||||
return new ApacheHttpTransport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeoCoderPresent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException {
|
||||
try {
|
||||
Class<? extends BluetoothDevice> c = bluetoothDevice.getClass();
|
||||
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
|
||||
insecure.setAccessible(true);
|
||||
return (BluetoothSocket) insecure.invoke(bluetoothDevice, 1);
|
||||
} catch (SecurityException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
Log.d(Constants.TAG, "Unable to create insecure connection", e);
|
||||
}
|
||||
return bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothConnectionManager.MY_TRACKS_UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideTitle(Activity activity) {
|
||||
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureActionBarHomeAsUp(Activity activity) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureListViewContextualMenu(Activity activity, ListView listView,
|
||||
ContextualActionModeCallback contextualActionModeCallback) {
|
||||
activity.registerForContextMenu(listView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureSearchWidget(
|
||||
Activity activity, MenuItem menuItem, TrackController trackController) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleSearchMenuSelection(Activity activity) {
|
||||
activity.onSearchRequested();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) {
|
||||
for (T item : items) {
|
||||
arrayAdapter.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidMenu(Activity activity) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpinnerBackgroundLight() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabBackground(TabWidget tabWidget) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleSearchKey(MenuItem menuItem) {
|
||||
// Return false and allow the framework to handle the search key.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGoogleFeedbackAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId) {
|
||||
return TrackWidgetProvider.HOME_SCREEN_DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId, int size) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void removeGlobalLayoutListener(
|
||||
ViewTreeObserver observer, OnGlobalLayoutListener listener) {
|
||||
observer.removeGlobalOnLayoutListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.location.Geocoder;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* API level 9 specific implementation of the {@link ApiAdapter}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@TargetApi(9)
|
||||
public class Api9Adapter extends Api8Adapter {
|
||||
|
||||
@Override
|
||||
public void applyPreferenceChanges(Editor editor) {
|
||||
// Apply asynchronously
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableStrictMode() {
|
||||
Log.d(Constants.TAG, "Enabling strict mode");
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] copyByteArray(byte[] input, int start, int end) {
|
||||
return Arrays.copyOfRange(input, start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpTransport getHttpTransport() {
|
||||
return new NetHttpTransport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeoCoderPresent() {
|
||||
return Geocoder.isPresent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.ContextualActionModeCallback;
|
||||
import com.google.android.apps.mytracks.TrackController;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A set of methods that may be implemented differently depending on the Android
|
||||
* API level.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public interface ApiAdapter {
|
||||
|
||||
/**
|
||||
* Applies all the changes done to a given preferences editor. Changes may or
|
||||
* may not be applied immediately.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*
|
||||
* @param editor the editor
|
||||
*/
|
||||
public void applyPreferenceChanges(SharedPreferences.Editor editor);
|
||||
|
||||
/**
|
||||
* Enables strict mode where supported, only if this is a development build.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
public void enableStrictMode();
|
||||
|
||||
/**
|
||||
* Copies elements from an input byte array into a new byte array, from
|
||||
* indexes start (inclusive) to end (exclusive). The end index must be less
|
||||
* than or equal to the input length.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*
|
||||
* @param input the input byte array
|
||||
* @param start the start index
|
||||
* @param end the end index
|
||||
* @return a new array containing elements from the input byte array.
|
||||
*/
|
||||
public byte[] copyByteArray(byte[] input, int start, int end);
|
||||
|
||||
/**
|
||||
* Gets a {@link HttpTransport}.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
public HttpTransport getHttpTransport();
|
||||
|
||||
/**
|
||||
* Returns true if GeoCoder is present.
|
||||
* <p>
|
||||
* Due to changes in API level 9.
|
||||
*/
|
||||
public boolean isGeoCoderPresent();
|
||||
|
||||
/**
|
||||
* Gets a {@link BluetoothSocket}.
|
||||
* <p>
|
||||
* Due to changes in API level 10.
|
||||
*
|
||||
* @param bluetoothDevice
|
||||
*/
|
||||
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException;
|
||||
|
||||
/**
|
||||
* Hides the title. If the platform supports the action bar, do nothing.
|
||||
* Ideally, with the action bar, we would like to collapse the navigation tabs
|
||||
* into the action bar. However, collapsing is not supported by the
|
||||
* compatibility library.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param activity the activity
|
||||
*/
|
||||
public void hideTitle(Activity activity);
|
||||
|
||||
/**
|
||||
* Configures the action bar with the Home button as an Up button. If the
|
||||
* platform doesn't support the action bar, do nothing.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param activity the activity
|
||||
*/
|
||||
public void configureActionBarHomeAsUp(Activity activity);
|
||||
|
||||
/**
|
||||
* Configures the list view context menu.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param listView the list view
|
||||
* @param contextualActionModeCallback the callback when an item is selected
|
||||
* in the contextual action mode
|
||||
*/
|
||||
public void configureListViewContextualMenu(Activity activity, ListView listView,
|
||||
ContextualActionModeCallback contextualActionModeCallback);
|
||||
|
||||
/**
|
||||
* Configures the search widget.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param menuItem the search menu item
|
||||
* @param trackController the track controller to hide/show when search widget
|
||||
* is expanded/collapsed. Can be null
|
||||
*/
|
||||
public void configureSearchWidget(
|
||||
Activity activity, MenuItem menuItem, TrackController trackController);
|
||||
|
||||
/**
|
||||
* Handles the search menu selection. Returns true if handled.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param activity the activity
|
||||
*/
|
||||
public boolean handleSearchMenuSelection(Activity activity);
|
||||
|
||||
/**
|
||||
* Adds all items to an array adapter.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*
|
||||
* @param arrayAdapter the array adapter
|
||||
* @param items list of items
|
||||
*/
|
||||
public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items);
|
||||
|
||||
/**
|
||||
* Invalidates the menu.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*/
|
||||
public void invalidMenu(Activity activity);
|
||||
|
||||
/**
|
||||
* True if the spinner background is light color.
|
||||
* <p>
|
||||
* Due to changes in API level 11.
|
||||
*/
|
||||
public boolean isSpinnerBackgroundLight();
|
||||
|
||||
/**
|
||||
* Sets the tab background.
|
||||
*
|
||||
* @param tabWidget the tab widget
|
||||
*/
|
||||
public void setTabBackground(TabWidget tabWidget);
|
||||
|
||||
/**
|
||||
* Handles the search key press. Returns true if handled.
|
||||
* <p>
|
||||
* Due to changes in API level 14.
|
||||
*
|
||||
* @param menu the search menu
|
||||
*/
|
||||
public boolean handleSearchKey(MenuItem menu);
|
||||
|
||||
/**
|
||||
* Returns true if Google Feedback is available.
|
||||
* <p>
|
||||
* Due to changes in ApI level 14.
|
||||
*/
|
||||
public boolean isGoogleFeedbackAvailable();
|
||||
|
||||
/**
|
||||
* Gets the app widget size.
|
||||
* <p>
|
||||
* Due to changes in API level 16.
|
||||
*
|
||||
* @param appWidgetManager the app widget manager
|
||||
* @param appWidgetId the app widget id
|
||||
*/
|
||||
public int getAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId);
|
||||
|
||||
/**
|
||||
* Sets the app widget size.
|
||||
* <p>
|
||||
* Due to changes in API level 16.
|
||||
*
|
||||
* @param appWidgetManager the app widget manager.
|
||||
* @param appWidgetId the app widgit id
|
||||
* @param size the size
|
||||
*/
|
||||
public void setAppWidgetSize(AppWidgetManager appWidgetManager, int appWidgetId, int size);
|
||||
|
||||
/**
|
||||
* Removes the global layout listener.
|
||||
* <p>
|
||||
* Due to changes in API level 16.
|
||||
*
|
||||
* @param observer the observer
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void removeGlobalLayoutListener(
|
||||
ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener listener);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* A factory to get the {@link ApiAdapter} for the current device.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiAdapterFactory {
|
||||
|
||||
private static ApiAdapter apiAdapter;
|
||||
|
||||
/**
|
||||
* Gets the {@link ApiAdapter} for the current device.
|
||||
*/
|
||||
public static ApiAdapter getApiAdapter() {
|
||||
if (apiAdapter == null) {
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
apiAdapter = new Api17Adapter();
|
||||
} else if (Build.VERSION.SDK_INT >= 16) {
|
||||
apiAdapter = new Api16Adapter();
|
||||
} else if (Build.VERSION.SDK_INT >= 14) {
|
||||
apiAdapter = new Api14Adapter();
|
||||
} else if (Build.VERSION.SDK_INT >= 11) {
|
||||
apiAdapter = new Api11Adapter();
|
||||
} else if (Build.VERSION.SDK_INT >= 10) {
|
||||
apiAdapter = new Api10Adapter();
|
||||
} else if (Build.VERSION.SDK_INT >= 9) {
|
||||
apiAdapter = new Api9Adapter();
|
||||
} else {
|
||||
apiAdapter = new Api8Adapter();
|
||||
}
|
||||
}
|
||||
return apiAdapter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with bluetooth devices.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class BluetoothDeviceUtils {
|
||||
|
||||
private BluetoothDeviceUtils() {}
|
||||
|
||||
/**
|
||||
* Populates the device names and the device addresses with all the suitable
|
||||
* bluetooth devices.
|
||||
*
|
||||
* @param bluetoothAdapter the bluetooth adapter
|
||||
* @param deviceNames list of device names
|
||||
* @param deviceAddresses list of device addresses
|
||||
*/
|
||||
public static void populateDeviceLists(
|
||||
BluetoothAdapter bluetoothAdapter, List<String> deviceNames, List<String> deviceAddresses) {
|
||||
// Ensure the bluetooth adapter is not in discovery mode.
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
|
||||
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
|
||||
for (BluetoothDevice device : pairedDevices) {
|
||||
BluetoothClass bluetoothClass = device.getBluetoothClass();
|
||||
if (bluetoothClass != null) {
|
||||
// Not really sure what we want, but I know what we don't want.
|
||||
switch (bluetoothClass.getMajorDeviceClass()) {
|
||||
case BluetoothClass.Device.Major.COMPUTER:
|
||||
case BluetoothClass.Device.Major.PHONE:
|
||||
break;
|
||||
default:
|
||||
deviceAddresses.add(device.getAddress());
|
||||
deviceNames.add(device.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
/**
|
||||
* Utils to calculate calories.
|
||||
*
|
||||
* @author youtaol
|
||||
*/
|
||||
public class CalorieUtils {
|
||||
|
||||
private CalorieUtils() {}
|
||||
|
||||
/**
|
||||
* Resting VO2 is constant for everyone and is equal to 3.5 milliliters per
|
||||
* kilogram of body weight per minute.
|
||||
*/
|
||||
private static final double RESTING_VO2 = 3.5;
|
||||
|
||||
/**
|
||||
* Ratio of change Liter to kcal. In kcal per Liter.
|
||||
*/
|
||||
private static final double L_TO_KCAL = 5;
|
||||
|
||||
private static final double earthGravity = 9.8;
|
||||
|
||||
/**
|
||||
* Lumped constant for all frictional losses (tires, bearings, chain)
|
||||
*/
|
||||
private static final double K1 = 0.0053;
|
||||
|
||||
/**
|
||||
* Lumped constant for aerodynamic drag
|
||||
*/
|
||||
private static final double K2 = 0.185;
|
||||
|
||||
/**
|
||||
* Critical speed running in meters per minute. Converts 4.5 miles per hour to
|
||||
* meters per minute. 4 miles per hour is regarded as the max speed of walking
|
||||
* and 5 miles per hour is regarded as the minimal speed of running, so we use
|
||||
* the 4.5 miles per hour as the critical seed. <a href=
|
||||
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
|
||||
* >Reference</a>
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
|
||||
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
|
||||
|
||||
public enum ActivityType {
|
||||
CYCLING, FOOT
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calorie of walking. This equation is appropriate for fairly
|
||||
* slow speed ranges—from 1.9 to approximately 4 miles per hour (mph).
|
||||
*
|
||||
* @param speed is calculated in meters per second (m/s)
|
||||
* @param grade
|
||||
* @return the VO2 value in ml/kg/min.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double getLowSpeedFootVo2(double speed, double grade) {
|
||||
// Change meters per second to meters per minute
|
||||
speed = speed / UnitConversions.S_TO_MIN;
|
||||
/*
|
||||
* 0.1 means oxygen cost per meter of moving each kilogram (kg) of body
|
||||
* weight while walking (horizontally). 1.8 means oxygen cost per meter of
|
||||
* moving total body mass against gravity (vertically).
|
||||
*/
|
||||
return 0.1 * speed + 1.8 * speed * Math.abs(grade) + RESTING_VO2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calorie of running. This equation is appropriate for speeds
|
||||
* greater than 5.0 mph (or 3.0 mph or greater if the subject is truly
|
||||
* jogging).
|
||||
*
|
||||
* @param speed is calculated in meters per second (m/s)
|
||||
* @param grade
|
||||
* @return the VO2 value in ml/kg/min.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double getHighSpeedFootVo2(double speed, double grade) {
|
||||
// Change meters per second to meters per minute
|
||||
speed = speed / UnitConversions.S_TO_MIN;
|
||||
/*
|
||||
* 0.2 means oxygen cost per meter of moving each kg of body weight while
|
||||
* running (horizontally). 0.9 means oxygen cost per meter of moving total
|
||||
* body mass against gravity (vertically).
|
||||
*/
|
||||
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calorie of cycling. Use below equation: <br>
|
||||
* P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
|
||||
* <ul>
|
||||
* <li>P - Power in watts(Joule/second)</li>
|
||||
* <li>g - gravity, using 9.8 m / s^2</li>
|
||||
* <li>m - mass of the rider plus bike, in kg</li>
|
||||
* <li>s - grade of surface</li>
|
||||
* <li>Vg - ground relative speed m/s</li>
|
||||
* <li>Va - air relative speed. We assume the air speed is zero, so Va is
|
||||
* equal with Vg.</li>
|
||||
* <li>K1 - a constant which represents frictional losses, using 0.0053.</li>
|
||||
* <li>K2 - a constant representing aerodynamic drag, using 0.185 kg/m.</li>
|
||||
* <li></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param speed is calculated in meters per second (m/s)
|
||||
* @param grade the grade to calculate
|
||||
* @param weight of rider plus bike, in kilogram
|
||||
* @param timeUsed how many times used in second
|
||||
* @return the power value watts(Joule/second).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double calculateCyclingCalorie(double speed, double grade, int weight, double timeUsed) {
|
||||
// Get the Power, the unit is Watt (Joule/second)
|
||||
double power = earthGravity * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
|
||||
|
||||
// Get the calories
|
||||
return power * timeUsed / UnitConversions.KCAL_TO_J;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calories by cycling.
|
||||
*
|
||||
* @param start the start location
|
||||
* @param stop the stop location
|
||||
* @param grade the grade to calculate
|
||||
* @param weight the weight of user, in kilogram
|
||||
* @return the calories between the start and stop location.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double calculateCalorieCycling(Location start, Location stop, double grade, int weight) {
|
||||
// Gets duration in seconds
|
||||
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S;
|
||||
// Get speed in meters per second
|
||||
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
|
||||
if (grade < 0) {
|
||||
grade = 0.0;
|
||||
}
|
||||
|
||||
return calculateCyclingCalorie(speed, grade, weight, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calories by running or walking.
|
||||
*
|
||||
* @param start the start location
|
||||
* @param stop the stop location
|
||||
* @param grade the grade to calculate
|
||||
* @param weight the weight of user, in kilogram
|
||||
* @return the calories between the start and stop location, in calories.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double calculateCalorieFoot(Location start, Location stop, double grade, int weight) {
|
||||
// Get speed in meters per second
|
||||
double averageSpeed = (start.getSpeed() + stop.getSpeed()) / 2.0;
|
||||
// Get VO2 in mL/kg/min
|
||||
double vo2 = getFootVo2(averageSpeed, grade);
|
||||
// Minutes
|
||||
double time = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S
|
||||
* UnitConversions.S_TO_MIN;
|
||||
// Get the calorie. The unit of calorie is kcal which is came from mL/kg/min
|
||||
// * min * kg * L/mL * kcal/L
|
||||
return vo2 * time * weight * UnitConversions.ML_TO_L * L_TO_KCAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the VO2 value.
|
||||
*
|
||||
* @param speed in meters per second
|
||||
* @param grade the grade to calculate
|
||||
* @return the VO2 value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static double getFootVo2(double speed, double grade) {
|
||||
if (grade < 0) {
|
||||
grade = 0.0;
|
||||
}
|
||||
|
||||
return speed > CRTICAL_SPEED_RUNNING ? getHighSpeedFootVo2(speed, grade) : getLowSpeedFootVo2(
|
||||
speed, grade);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the calories between two locations.
|
||||
*
|
||||
* @param start the start location
|
||||
* @param stop the stop location
|
||||
* @param grade the grade to calculate
|
||||
* @param weight the weight of user
|
||||
* @param activityType can be foot or cycling
|
||||
* @return the calories between the start and stop location.
|
||||
*/
|
||||
public static double getCalorie(Location start, Location stop, double grade, int weight,
|
||||
ActivityType activityType) {
|
||||
return ActivityType.CYCLING == activityType ? calculateCalorieCycling(start, stop, grade,
|
||||
weight) : calculateCalorieFoot(start, stop, grade, weight);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2009 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* This class will generate google chart server url's.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ChartURLGenerator {
|
||||
|
||||
private static final String CHARTS_BASE_URL = "http://chart.apis.google.com/chart?";
|
||||
|
||||
private ChartURLGenerator() {}
|
||||
|
||||
/**
|
||||
* Gets a chart of a track.
|
||||
*
|
||||
* @param distances An array of distance measurements
|
||||
* @param elevations A matching array of elevation measurements
|
||||
* @param track The track for this chart
|
||||
* @param context The current appplication context
|
||||
*/
|
||||
public static String getChartUrl(
|
||||
Vector<Double> distances, Vector<Double> elevations, Track track, Context context) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
return getChartUrl(
|
||||
distances, elevations, track, context.getString(R.string.stats_elevation), metricUnits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a chart of a track. This form is for testing without contexts.
|
||||
*
|
||||
* @param distances An array of distance measurements
|
||||
* @param elevations A matching array of elevation measurements
|
||||
* @param track The track for this chart
|
||||
* @param title The title for the chart
|
||||
* @param metricUnits Should the data be displayed in metric units
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getChartUrl(Vector<Double> distances, Vector<Double> elevations, Track track,
|
||||
String title, boolean metricUnits) {
|
||||
if (distances == null || elevations == null || track == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (distances.size() != elevations.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Round it up.
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
double effectiveMaxY = tripStatistics.getMaxElevation();
|
||||
if (!metricUnits) {
|
||||
effectiveMaxY *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
effectiveMaxY = ((int) (effectiveMaxY / 100)) * 100 + 100;
|
||||
|
||||
// Round it down.
|
||||
double effectiveMinY = tripStatistics.getMinElevation();
|
||||
if (!metricUnits) {
|
||||
effectiveMinY *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
effectiveMinY = ((int) (effectiveMinY / 100)) * 100;
|
||||
if (tripStatistics.getMinElevation() < 0) {
|
||||
effectiveMinY -= 100;
|
||||
}
|
||||
|
||||
double ySpread = effectiveMaxY - effectiveMinY;
|
||||
|
||||
StringBuilder sb = new StringBuilder(CHARTS_BASE_URL);
|
||||
sb.append("&chs=600x350");
|
||||
sb.append("&cht=lxy");
|
||||
|
||||
// Title
|
||||
sb.append("&chtt=");
|
||||
sb.append(title);
|
||||
|
||||
// Labels
|
||||
sb.append("&chxt=x,y");
|
||||
double totalDistance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
totalDistance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
int xInterval = ((int) (totalDistance / 6));
|
||||
int yInterval = ((int) (ySpread / 600)) * 100;
|
||||
if (yInterval < 100) {
|
||||
yInterval = 25;
|
||||
}
|
||||
// Range
|
||||
sb.append("&chxr=0,0,");
|
||||
sb.append((int) totalDistance);
|
||||
sb.append(',');
|
||||
sb.append(xInterval);
|
||||
|
||||
sb.append("|1,");
|
||||
sb.append(effectiveMinY);
|
||||
sb.append(',');
|
||||
sb.append(effectiveMaxY);
|
||||
sb.append(',');
|
||||
sb.append(yInterval);
|
||||
|
||||
// Line color
|
||||
sb.append("&chco=009A00");
|
||||
|
||||
// Fill
|
||||
sb.append("&chm=B,00AA00,0,0,0");
|
||||
|
||||
// Grid lines
|
||||
double desiredGrids = ySpread / yInterval;
|
||||
sb.append("&chg=100000,");
|
||||
sb.append(100.0 / desiredGrids);
|
||||
sb.append(",1,0");
|
||||
|
||||
// Data
|
||||
sb.append("&chd=e:");
|
||||
for (int i = 0; i < distances.size(); i++) {
|
||||
int normalized = (int) (getNormalizedDistance(distances.elementAt(i), track) * 4095);
|
||||
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
|
||||
}
|
||||
|
||||
sb.append(ChartsExtendedEncoder.getSeparator());
|
||||
for (int i = 0; i < elevations.size(); i++) {
|
||||
double value = elevations.elementAt(i);
|
||||
if (!metricUnits) {
|
||||
value *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
int normalized = (int) (getNormalizedElevation(value, effectiveMinY, ySpread) * 4095);
|
||||
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static double getNormalizedDistance(double value, Track track) {
|
||||
return value / track.getTripStatistics().getTotalDistance();
|
||||
}
|
||||
|
||||
private static double getNormalizedElevation(double value, double effectiveMinY, double ySpread) {
|
||||
return (value - effectiveMinY) / ySpread;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2009 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.util;
|
||||
|
||||
/**
|
||||
* The singleton class representing the extended encoding of chart data.
|
||||
*/
|
||||
public class ChartsExtendedEncoder {
|
||||
|
||||
// ChartServer data encoding in extended mode
|
||||
private static final String CHARTSERVER_EXTENDED_ENCODING_SEPARATOR = ",";
|
||||
private static final String CHARTSERVER_EXTENDED_ENCODING =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";
|
||||
private static final int CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES =
|
||||
CHARTSERVER_EXTENDED_ENCODING.length();
|
||||
private static final String MISSING_POINT_EXTENDED_ENCODING = "__";
|
||||
|
||||
private ChartsExtendedEncoder() { }
|
||||
|
||||
public static String getEncodedValue(int scaled) {
|
||||
int index1 = scaled / CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||
if (index1 < 0 || index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
return MISSING_POINT_EXTENDED_ENCODING;
|
||||
}
|
||||
|
||||
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||
if (index2 < 0 || index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
return MISSING_POINT_EXTENDED_ENCODING;
|
||||
}
|
||||
|
||||
return String.valueOf(CHARTSERVER_EXTENDED_ENCODING.charAt(index1))
|
||||
+ String.valueOf(CHARTSERVER_EXTENDED_ENCODING.charAt(index2));
|
||||
}
|
||||
|
||||
public static String getSeparator() {
|
||||
return CHARTSERVER_EXTENDED_ENCODING_SEPARATOR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Utilities for creating dialogs.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class DialogUtils {
|
||||
|
||||
private DialogUtils() {}
|
||||
|
||||
/**
|
||||
* Creates a confirmation dialog.
|
||||
*
|
||||
* @param context the context
|
||||
* @param titleId the title
|
||||
* @param message the message
|
||||
* @param okListener the listener when OK is clicked
|
||||
*/
|
||||
public static Dialog createConfirmationDialog(
|
||||
Context context, int titleId, String message, DialogInterface.OnClickListener okListener) {
|
||||
return createConfirmationDialog(context, titleId, message, null, okListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a confirmation dialog.
|
||||
*
|
||||
* @param context the context
|
||||
* @param titleId the title
|
||||
* @param view the view
|
||||
* @param okListener the listener when OK is clicked
|
||||
*/
|
||||
public static Dialog createConfirmationDialog(
|
||||
Context context, int titleId, View view, DialogInterface.OnClickListener okListener) {
|
||||
return createConfirmationDialog(context, titleId, null, view, okListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spinner progress dialog.
|
||||
*
|
||||
* @param context the context
|
||||
* @param messageId the progress message id
|
||||
* @param onCancelListener the cancel listener
|
||||
*/
|
||||
public static ProgressDialog createSpinnerProgressDialog(
|
||||
Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) {
|
||||
return createProgressDialog(true, context, messageId, onCancelListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a horizontal progress dialog.
|
||||
*
|
||||
* @param context the context
|
||||
* @param messageId the progress message id
|
||||
* @param onCancelListener the cancel listener
|
||||
* @param formatArgs the format arguments for the messageId
|
||||
*/
|
||||
public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId,
|
||||
DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
|
||||
return createProgressDialog(false, context, messageId, onCancelListener, formatArgs);
|
||||
}
|
||||
|
||||
private static Dialog createConfirmationDialog(Context context, int titleId, String message,
|
||||
View view, DialogInterface.OnClickListener okListener) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context).setCancelable(true)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert).setNegativeButton(R.string.generic_no, null)
|
||||
.setPositiveButton(R.string.generic_yes, okListener).setTitle(titleId);
|
||||
if (message != null) {
|
||||
builder.setMessage(message);
|
||||
}
|
||||
if (view != null) {
|
||||
builder.setView(view);
|
||||
}
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a progress dialog.
|
||||
*
|
||||
* @param spinner true to use the spinner style
|
||||
* @param context the context
|
||||
* @param messageId the progress message id
|
||||
* @param onCancelListener the cancel listener
|
||||
* @param formatArgs the format arguments for the message id
|
||||
*/
|
||||
private static ProgressDialog createProgressDialog(boolean spinner, Context context,
|
||||
int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) {
|
||||
ProgressDialog progressDialog = new ProgressDialog(context, R.style.MyTracks);
|
||||
progressDialog.setCancelable(true);
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progressDialog.setIndeterminate(true);
|
||||
progressDialog.setMessage(context.getString(messageId, formatArgs));
|
||||
progressDialog.setOnCancelListener(onCancelListener);
|
||||
progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER
|
||||
: ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setTitle(R.string.generic_progress_title);
|
||||
return progressDialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2008 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
/**
|
||||
* Utilities for EULA.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class EulaUtils {
|
||||
|
||||
private static final String EULA_PREFERENCE_FILE = "eula";
|
||||
|
||||
// Accepting Google mobile terms of service
|
||||
private static final String ACCEPT_EULA_PREFERENCE_KEY = "eula.google_mobile_tos_accepted";
|
||||
private static final String DEFAULT_UNITS_PREFERENCE_KEY = "defaultUnits";
|
||||
private static final String SHOW_ENABLE_SYNC_PREFERENCE_KEY = "showEnableSync";
|
||||
|
||||
private EulaUtils() {}
|
||||
|
||||
/**
|
||||
* Returns true if eula is accepted.
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean hasAcceptEula(Context context) {
|
||||
return getValue(context, ACCEPT_EULA_PREFERENCE_KEY, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true that eula is accepted.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void setAcceptEula(Context context) {
|
||||
setValue(context, ACCEPT_EULA_PREFERENCE_KEY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the default units is initialized.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean hasDefaultUnits(Context context) {
|
||||
return getValue(context, DEFAULT_UNITS_PREFERENCE_KEY, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to true that the default units is initialized.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void setDefaultUnits(Context context) {
|
||||
setValue(context, DEFAULT_UNITS_PREFERENCE_KEY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if show enable sync is needed.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean hasShowEnableSync(Context context) {
|
||||
return getValue(context, SHOW_ENABLE_SYNC_PREFERENCE_KEY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to false that show enable sync is needed.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void setShowEnableSync(Context context) {
|
||||
setValue(context, SHOW_ENABLE_SYNC_PREFERENCE_KEY, false);
|
||||
}
|
||||
|
||||
private static boolean getValue(Context context, String key, boolean defaultValue) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
private static void setValue(Context context, String key, boolean value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
EULA_PREFERENCE_FILE, Context.MODE_PRIVATE);
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putBoolean(key, value);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with files.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils() {}
|
||||
|
||||
public static final String TEMP_DIR = "tmp";
|
||||
public static final String BACKUPS_DIR = "backups";
|
||||
|
||||
/**
|
||||
* Name of the top-level directory inside the SD card where our files will be
|
||||
* read from/written to.
|
||||
*/
|
||||
public static final String SDCARD_TOP_DIR = "MyTracks";
|
||||
|
||||
/**
|
||||
* The maximum FAT32 path length. See the FAT32 spec at
|
||||
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final int MAX_FAT32_PATH_LENGTH = 260;
|
||||
|
||||
/**
|
||||
* Returns true if the external storage is available.
|
||||
*/
|
||||
public static boolean isExternalStorageAvailable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state)
|
||||
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the external storage is writable.
|
||||
*/
|
||||
public static boolean isExternalStorageWriteable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the directory exists.
|
||||
*
|
||||
* @param dir the directory
|
||||
*/
|
||||
public static boolean isDirectory(File dir) {
|
||||
return dir.exists() && dir.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the directory exists by creating it and its parents if
|
||||
* necessary.
|
||||
*
|
||||
* @return whether the directory exists (either already existed or was
|
||||
* successfully created)
|
||||
*/
|
||||
public static boolean ensureDirectoryExists(File dir) {
|
||||
if (isDirectory(dir)) {
|
||||
return true;
|
||||
}
|
||||
return dir.mkdirs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory display name.
|
||||
*
|
||||
* @param components the components
|
||||
*/
|
||||
public static String getDirectoryDisplayName(String... components) {
|
||||
StringBuilder dirNameBuilder = new StringBuilder();
|
||||
dirNameBuilder.append(File.separatorChar);
|
||||
dirNameBuilder.append(SDCARD_TOP_DIR);
|
||||
for (String component : components) {
|
||||
dirNameBuilder.append(File.separatorChar);
|
||||
dirNameBuilder.append(component);
|
||||
}
|
||||
return dirNameBuilder.toString();
|
||||
}
|
||||
/**
|
||||
* Gets the directory path.
|
||||
*
|
||||
* @param components the components
|
||||
*/
|
||||
public static String getDirectoryPath(String... components) {
|
||||
StringBuilder dirNameBuilder = new StringBuilder();
|
||||
dirNameBuilder.append(Environment.getExternalStorageDirectory());
|
||||
dirNameBuilder.append(getDirectoryDisplayName(components));
|
||||
return dirNameBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filename with the given base name (prefix) and the given
|
||||
* extension, possibly adding a suffix to ensure the file doesn't exist.
|
||||
*
|
||||
* @param directory the directory the file will live in
|
||||
* @param fileBaseName the prefix for the file name
|
||||
* @param extension the file's extension
|
||||
* @return the complete file name, without the directory
|
||||
*/
|
||||
public static synchronized String buildUniqueFileName(
|
||||
File directory, String fileBaseName, String extension) {
|
||||
return buildUniqueFileName(directory, fileBaseName, extension, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filename with the given base and the given extension, possibly
|
||||
* adding a suffix to ensure the file doesn't exist.
|
||||
*
|
||||
* @param directory the directory the filename will be located in
|
||||
* @param base the base for the filename
|
||||
* @param extension the extension for the filename
|
||||
* @param suffix the first numeric suffix to try to use, or 0 for none
|
||||
* @return the complete filename, without the directory
|
||||
*/
|
||||
private static String buildUniqueFileName(
|
||||
File directory, String base, String extension, int suffix) {
|
||||
String suffixName = "";
|
||||
if (suffix > 0) {
|
||||
suffixName += "(" + Integer.toString(suffix) + ")";
|
||||
}
|
||||
suffixName += "." + extension;
|
||||
|
||||
String baseName = sanitizeFileName(base);
|
||||
baseName = truncateFileName(directory, baseName, suffixName);
|
||||
String fullName = baseName + suffixName;
|
||||
|
||||
if (!new File(directory, fullName).exists()) {
|
||||
return fullName;
|
||||
}
|
||||
return buildUniqueFileName(directory, base, extension, suffix + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the name as a valid fat32 filename. For simplicity, fat32
|
||||
* filename characters may be any combination of letters, digits, or
|
||||
* characters with code point values greater than 127. Replaces the invalid
|
||||
* characters with "_" and collapses multiple "_" together.
|
||||
*
|
||||
* @param name name
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String sanitizeFileName(String name) {
|
||||
StringBuffer buffer = new StringBuffer(name.length());
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
int codePoint = name.codePointAt(i);
|
||||
char character = name.charAt(i);
|
||||
if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) {
|
||||
buffer.appendCodePoint(codePoint);
|
||||
} else {
|
||||
buffer.append("_");
|
||||
}
|
||||
}
|
||||
String result = buffer.toString();
|
||||
return result.replaceAll("_+", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it is a special FAT32 character.
|
||||
*
|
||||
* @param character the character
|
||||
*/
|
||||
private static 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 +
|
||||
* name + suffix) meets the Fat32 path limit.
|
||||
*
|
||||
* @param directory directory
|
||||
* @param name name
|
||||
* @param suffix suffix
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String truncateFileName(File directory, String name, String suffix) {
|
||||
// 1 at the end accounts for the FAT32 filename trailing NUL character
|
||||
int requiredLength = directory.getPath().length() + suffix.length() + 1;
|
||||
if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) {
|
||||
int limit = MAX_FAT32_PATH_LENGTH - requiredLength;
|
||||
return name.substring(0, limit);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for Google Earth.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class GoogleEarthUtils {
|
||||
|
||||
private static final String GOOGLE_EARTH_CLASS = "com.google.earth.EarthActivity";
|
||||
private static final String GOOGLE_EARTH_KML_MIME_TYPE = "application/vnd.google-earth.kml+xml";
|
||||
private static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
|
||||
private static final String
|
||||
GOOGLE_EARTH_TOUR_FEATURE_ID = "com.google.earth.EXTRA.tour_feature_id";
|
||||
|
||||
public static final String GOOGLE_EARTH_MARKET_URL = "market://details?id="
|
||||
+ GOOGLE_EARTH_PACKAGE;
|
||||
public static final String TOUR_FEATURE_ID_VALUE = "tour";
|
||||
|
||||
private GoogleEarthUtils() {}
|
||||
|
||||
/**
|
||||
* Returns true if Google Earth is installed.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isEarthInstalled(Context context) {
|
||||
List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(
|
||||
new Intent().setType(GOOGLE_EARTH_KML_MIME_TYPE), PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo info : infos) {
|
||||
if (info.activityInfo != null && info.activityInfo.packageName != null
|
||||
&& info.activityInfo.packageName.equals(GOOGLE_EARTH_PACKAGE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an intent to play a kml file in Google Earth.
|
||||
*
|
||||
* @param kmlFilePath the kml file path
|
||||
*/
|
||||
public static Intent getPlayInEarthIntent(String kmlFilePath) {
|
||||
return new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(GOOGLE_EARTH_TOUR_FEATURE_ID, TOUR_FEATURE_ID_VALUE)
|
||||
.setClassName(GOOGLE_EARTH_PACKAGE, GOOGLE_EARTH_CLASS)
|
||||
.setDataAndType(Uri.fromFile(new File(kmlFilePath)), GOOGLE_EARTH_KML_MIME_TYPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Utilities for Google Feedback.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class GoogleFeedbackUtils {
|
||||
|
||||
private static final String TAG = GoogleFeedbackUtils.class.getSimpleName();
|
||||
|
||||
private GoogleFeedbackUtils() {}
|
||||
|
||||
/**
|
||||
* Binds the Google Feedback service.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static void bindFeedback(Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
|
||||
ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
try {
|
||||
service.transact(Binder.FIRST_CALL_TRANSACTION, Parcel.obtain(), null, 0);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {}
|
||||
};
|
||||
// Bind to the service after creating it if necessary
|
||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
|
||||
/**
|
||||
* Utilities for Google location settings
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class GoogleLocationUtils {
|
||||
|
||||
public static final String
|
||||
ACTION_GOOGLE_LOCATION_SETTINGS = "com.google.android.gsf.GOOGLE_LOCATION_SETTINGS";
|
||||
private static final String
|
||||
ACTION_GOOGLE_APPS_LOCATION_SETTINGS = "com.google.android.gsf.GOOGLE_APPS_LOCATION_SETTINGS";
|
||||
|
||||
private GoogleLocationUtils() {}
|
||||
|
||||
/**
|
||||
* Returns true if the Google location settings is available.
|
||||
*/
|
||||
public static boolean isAvailable(Context context) {
|
||||
Intent intent = new Intent(ACTION_GOOGLE_APPS_LOCATION_SETTINGS);
|
||||
ResolveInfo resolveInfo = context.getPackageManager()
|
||||
.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
return resolveInfo != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Utilities for creating intents.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class IntentUtils {
|
||||
|
||||
public static final String TEXT_PLAIN_TYPE = "text/plain";
|
||||
private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
|
||||
private static final String TWITTER_PACKAGE_NAME = "com.twitter.android";
|
||||
|
||||
private IntentUtils() {}
|
||||
|
||||
/**
|
||||
* Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and
|
||||
* {@link Intent#FLAG_ACTIVITY_NEW_TASK}.
|
||||
*
|
||||
* @param context the context
|
||||
* @param cls the class
|
||||
*/
|
||||
public static final Intent newIntent(Context context, Class<?> cls) {
|
||||
return new Intent(context, cls).addFlags(
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an intent to share a track url with an app.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackId the track id
|
||||
* @param trackUrl the track url
|
||||
* @param packageName the sharing app package name
|
||||
* @param className the sharing app class name
|
||||
*/
|
||||
public static final Intent newShareUrlIntent(
|
||||
Context context, long trackId, String trackUrl, String packageName, String className) {
|
||||
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(trackId);
|
||||
String trackDescription = track == null ? ""
|
||||
: new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false);
|
||||
boolean urlOnly = TWITTER_PACKAGE_NAME.equals(packageName)
|
||||
|| BLUETOOTH_PACKAGE_NAME.equals(packageName);
|
||||
|
||||
return new Intent(Intent.ACTION_SEND)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
|
||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
|
||||
.putExtra(Intent.EXTRA_TEXT, urlOnly
|
||||
? trackUrl
|
||||
: context.getString(R.string.share_track_share_url_body, trackUrl, trackDescription))
|
||||
.setComponent(new ComponentName(packageName, className))
|
||||
.setType(TEXT_PLAIN_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an intent to share a track file with an app.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackId the track id
|
||||
* @param filePath the file path
|
||||
* @param trackFileFormat the track file format
|
||||
*/
|
||||
public static final Intent newShareFileIntent(
|
||||
Context context, long trackId, String filePath, TrackFileFormat trackFileFormat) {
|
||||
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(trackId);
|
||||
String trackDescription = track == null ? ""
|
||||
: new DescriptionGeneratorImpl(context).generateTrackDescription(track, null, null, false);
|
||||
|
||||
return new Intent(Intent.ACTION_SEND)
|
||||
.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath)))
|
||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
|
||||
.putExtra(Intent.EXTRA_TEXT,
|
||||
context.getString(R.string.share_track_share_file_body, trackDescription))
|
||||
.putExtra(context.getString(R.string.track_id_broadcast_extra), trackId)
|
||||
.setType(trackFileFormat.getMimeType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Utilities to display a list item.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ListItemUtils {
|
||||
|
||||
private ListItemUtils() {}
|
||||
|
||||
/**
|
||||
* Sets a list item.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param view the list item view
|
||||
* @param isRecording true if recording
|
||||
* @param isPaused true if paused
|
||||
* @param iconId the icon id
|
||||
* @param iconContentDescriptionId the icon content description id
|
||||
* @param name the name value
|
||||
* @param sharedOwner if shared with me track, the owner, else null
|
||||
* @param totalTime the total time value
|
||||
* @param totalDistance the total distance value
|
||||
* @param photoUrl the photo url
|
||||
* @param startTime the start time value
|
||||
* @param category the category value
|
||||
* @param description the description value
|
||||
*/
|
||||
public static void setListItem(Activity activity, View view, boolean isRecording,
|
||||
boolean isPaused, int iconId, int iconContentDescriptionId, String name, String sharedOwner,
|
||||
String totalTime, String totalDistance, String photoUrl, long startTime, String category,
|
||||
String description) {
|
||||
|
||||
if (isRecording) {
|
||||
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
|
||||
iconContentDescriptionId = isPaused ? R.string.icon_pause_recording
|
||||
: R.string.icon_record_track;
|
||||
}
|
||||
|
||||
ImageView iconImageView = (ImageView) view.findViewById(R.id.list_item_icon);
|
||||
iconImageView.setImageResource(iconId);
|
||||
iconImageView.setContentDescription(activity.getString(iconContentDescriptionId));
|
||||
|
||||
// Set name
|
||||
TextView nameTextView = (TextView) view.findViewById(R.id.list_item_name);
|
||||
nameTextView.setText(name);
|
||||
|
||||
// Set sharedOwner/totalTime/totalDistance
|
||||
TextView timeDistanceTextView = (TextView) view.findViewById(R.id.list_item_time_distance);
|
||||
if (isRecording) {
|
||||
timeDistanceTextView.setTextColor(activity.getResources()
|
||||
.getColor(isPaused ? android.R.color.white : R.color.recording_text));
|
||||
} else {
|
||||
// Need to match the style set in list_item.xml
|
||||
timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall);
|
||||
}
|
||||
setTextView(timeDistanceTextView,
|
||||
getTimeDistance(activity, isRecording, isPaused, sharedOwner, totalTime, totalDistance));
|
||||
|
||||
// Set photoUrl
|
||||
ImageView photo = (ImageView) view.findViewById(R.id.list_item_photo);
|
||||
if (photoUrl == null || photoUrl.equals("")) {
|
||||
photo.setVisibility(View.GONE);
|
||||
} else {
|
||||
photo.setVisibility(View.VISIBLE);
|
||||
Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
|
||||
// Set the initial width to 33% of the display width.
|
||||
int width = (int) (defaultDisplay.getWidth() * .33);
|
||||
int height = 0;
|
||||
PhotoUtils.setImageVew(photo, Uri.parse(photoUrl), width, height);
|
||||
}
|
||||
|
||||
// Set date/time
|
||||
String[] startTimeDisplay = getStartTime(isRecording, activity, startTime);
|
||||
TextView dateTextView = (TextView) view.findViewById(R.id.list_item_date);
|
||||
setTextView(dateTextView, startTimeDisplay[0]);
|
||||
|
||||
TextView timeTextView = (TextView) view.findViewById(R.id.list_item_time);
|
||||
setTextView(timeTextView, startTimeDisplay[1]);
|
||||
|
||||
// Set category/description
|
||||
TextView descriptionTextView = (TextView) view.findViewById(R.id.list_item_description);
|
||||
setTextView(descriptionTextView,
|
||||
isRecording ? null : StringUtils.getCategoryDescription(category, description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time/distance text.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording true if recording
|
||||
* @param isPaused true if paused
|
||||
* @param sharedOwner the shared owner
|
||||
* @param totalTime the total time
|
||||
* @param totalDistance the total distance
|
||||
*/
|
||||
private static String getTimeDistance(Context context, boolean isRecording, boolean isPaused,
|
||||
String sharedOwner, String totalTime, String totalDistance) {
|
||||
if (isRecording) {
|
||||
return context.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
if (sharedOwner != null && sharedOwner.length() != 0) {
|
||||
buffer.append(sharedOwner);
|
||||
}
|
||||
if (totalTime != null && totalTime.length() != 0) {
|
||||
if (buffer.length() != 0) {
|
||||
buffer.append(" \u2027 ");
|
||||
}
|
||||
buffer.append(totalTime);
|
||||
}
|
||||
if (totalDistance != null && totalDistance.length() != 0) {
|
||||
if (buffer.length() != 0) {
|
||||
buffer.append(" ");
|
||||
}
|
||||
buffer.append("(" + totalDistance + ")");
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start time text.
|
||||
*
|
||||
* @param isRecording true if recording
|
||||
* @param context the context
|
||||
* @param startTime the start time
|
||||
* @return array of two strings.
|
||||
*/
|
||||
private static String[] getStartTime(boolean isRecording, Context context, long startTime) {
|
||||
if (isRecording || startTime == 0L) {
|
||||
return new String[] { null, null };
|
||||
}
|
||||
if (DateUtils.isToday(startTime)) {
|
||||
return new String[] { DateUtils.getRelativeTimeSpanString(
|
||||
startTime, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE).toString(), null };
|
||||
}
|
||||
int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL;
|
||||
if (!isThisYear(startTime)) {
|
||||
flags |= DateUtils.FORMAT_NUMERIC_DATE;
|
||||
}
|
||||
return new String[] { DateUtils.formatDateTime(context, startTime, flags),
|
||||
DateUtils.formatDateTime(
|
||||
context, startTime, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_TIME) };
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the time is this year.
|
||||
*
|
||||
* @param time the time
|
||||
*/
|
||||
private static boolean isThisYear(long time) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
now.setTimeInMillis(System.currentTimeMillis());
|
||||
calendar.setTimeInMillis(time);
|
||||
return now.get(Calendar.YEAR) == calendar.get(Calendar.YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a text view.
|
||||
*
|
||||
* @param textView the text view
|
||||
* @param value the value for the text view
|
||||
*/
|
||||
private static void setTextView(TextView textView, String value) {
|
||||
if (value == null || value.length() == 0) {
|
||||
textView.setVisibility(View.GONE);
|
||||
} else {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2008 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Utility class for decimating tracks at a given level of precision.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class LocationUtils {
|
||||
|
||||
private static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute
|
||||
|
||||
private LocationUtils() {}
|
||||
|
||||
/**
|
||||
* Computes the distance on the two sphere between the point c0 and the line
|
||||
* segment c1 to c2.
|
||||
*
|
||||
* @param c0 the first coordinate
|
||||
* @param c1 the beginning of the line segment
|
||||
* @param c2 the end of the lone segment
|
||||
* @return the distance in m (assuming spherical earth)
|
||||
*/
|
||||
private static double distance(final Location c0, final Location c1, final Location c2) {
|
||||
if (c1.equals(c2)) {
|
||||
return c2.distanceTo(c0);
|
||||
}
|
||||
|
||||
final double s0lat = c0.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s0lng = c0.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s1lat = c1.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s1lng = c1.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s2lat = c2.getLatitude() * UnitConversions.DEG_TO_RAD;
|
||||
final double s2lng = c2.getLongitude() * UnitConversions.DEG_TO_RAD;
|
||||
|
||||
double s2s1lat = s2lat - s1lat;
|
||||
double s2s1lng = s2lng - s1lng;
|
||||
final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng)
|
||||
/ (s2s1lat * s2s1lat + s2s1lng * s2s1lng);
|
||||
if (u <= 0) {
|
||||
return c0.distanceTo(c1);
|
||||
}
|
||||
if (u >= 1) {
|
||||
return c0.distanceTo(c2);
|
||||
}
|
||||
Location sa = new Location("");
|
||||
sa.setLatitude(c0.getLatitude() - c1.getLatitude());
|
||||
sa.setLongitude(c0.getLongitude() - c1.getLongitude());
|
||||
Location sb = new Location("");
|
||||
sb.setLatitude(u * (c2.getLatitude() - c1.getLatitude()));
|
||||
sb.setLongitude(u * (c2.getLongitude() - c1.getLongitude()));
|
||||
return sa.distanceTo(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the given locations for a given zoom level. This uses a
|
||||
* Douglas-Peucker decimation algorithm.
|
||||
*
|
||||
* @param tolerance in meters
|
||||
* @param locations input
|
||||
* @param decimated output
|
||||
*/
|
||||
private static void decimate(
|
||||
double tolerance, ArrayList<Location> locations, ArrayList<Location> decimated) {
|
||||
final int n = locations.size();
|
||||
if (n < 1) {
|
||||
return;
|
||||
}
|
||||
int idx;
|
||||
int maxIdx = 0;
|
||||
Stack<int[]> stack = new Stack<int[]>();
|
||||
double[] dists = new double[n];
|
||||
dists[0] = 1;
|
||||
dists[n - 1] = 1;
|
||||
double maxDist;
|
||||
double dist = 0.0;
|
||||
int[] current;
|
||||
|
||||
if (n > 2) {
|
||||
int[] stackVal = new int[] { 0, (n - 1) };
|
||||
stack.push(stackVal);
|
||||
while (stack.size() > 0) {
|
||||
current = stack.pop();
|
||||
maxDist = 0;
|
||||
for (idx = current[0] + 1; idx < current[1]; ++idx) {
|
||||
dist = LocationUtils.distance(
|
||||
locations.get(idx), locations.get(current[0]), locations.get(current[1]));
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist;
|
||||
maxIdx = idx;
|
||||
}
|
||||
}
|
||||
if (maxDist > tolerance) {
|
||||
dists[maxIdx] = maxDist;
|
||||
int[] stackValCurMax = { current[0], maxIdx };
|
||||
stack.push(stackValCurMax);
|
||||
int[] stackValMaxCur = { maxIdx, current[1] };
|
||||
stack.push(stackValMaxCur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
idx = 0;
|
||||
decimated.clear();
|
||||
for (Location l : locations) {
|
||||
if (dists[idx] != 0) {
|
||||
decimated.add(l);
|
||||
i++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
Log.d(Constants.TAG, "Decimating " + n + " points to " + i + " w/ tolerance = " + tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the given track for the given precision.
|
||||
*
|
||||
* @param track a track
|
||||
* @param precision desired precision in meters
|
||||
*/
|
||||
public static void decimate(Track track, double precision) {
|
||||
ArrayList<Location> decimated = new ArrayList<Location>();
|
||||
decimate(precision, track.getLocations(), decimated);
|
||||
track.setLocations(decimated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given location is a valid (i.e. physically possible) location
|
||||
* on Earth. Note: The special separator locations (which have latitude = 100)
|
||||
* will not qualify as valid. Neither will locations with lat=0 and lng=0 as
|
||||
* these are most likely "bad" measurements which often cause trouble.
|
||||
*
|
||||
* @param location the location to test
|
||||
* @return true if the location is a valid location.
|
||||
*/
|
||||
public static boolean isValidLocation(Location location) {
|
||||
return location != null && Math.abs(location.getLatitude()) <= 90
|
||||
&& Math.abs(location.getLongitude()) <= 180;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a location is old.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
public static boolean isLocationOld(Location location) {
|
||||
return !LocationUtils.isValidLocation(location)
|
||||
|| (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2013 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.services.tasks.BitmapLoader;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Utilities for photos.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class PhotoUtils {
|
||||
|
||||
private PhotoUtils() {}
|
||||
|
||||
/**
|
||||
* Sets an image view.
|
||||
*
|
||||
* @param imageView the image view
|
||||
* @param uri the image uri
|
||||
* @param displayWidth the display width
|
||||
* @param displayHeight the display height
|
||||
*/
|
||||
public static void setImageVew(
|
||||
ImageView imageView, Uri uri, int displayWidth, int displayHeight) {
|
||||
if (cancelBitmapLoader(imageView, uri)) {
|
||||
BitmapLoader bitmapLoader = new BitmapLoader(imageView, uri, displayWidth, displayHeight);
|
||||
WeakReference<BitmapLoader> bitmapLoaderReference = new WeakReference<BitmapLoader>(
|
||||
bitmapLoader);
|
||||
imageView.setTag(bitmapLoaderReference);
|
||||
bitmapLoader.execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image view bitmap loader.
|
||||
*
|
||||
* @param imageView the image view
|
||||
*/
|
||||
public static BitmapLoader getBitmapLoader(ImageView imageView) {
|
||||
if (imageView != null) {
|
||||
Object object = imageView.getTag();
|
||||
if (object instanceof WeakReference<?>) {
|
||||
@SuppressWarnings("unchecked")
|
||||
WeakReference<BitmapLoader> bitmapLoaderReference = (WeakReference<BitmapLoader>) object;
|
||||
return bitmapLoaderReference.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the image view bitmap loader.
|
||||
*
|
||||
* @param imageView the image view
|
||||
* @param uri the uri
|
||||
* @return false if the bitmap loader shouldn't be canceled. True if there is
|
||||
* no bitmap loader or the bitmap loader is cancelled.
|
||||
*/
|
||||
private static boolean cancelBitmapLoader(ImageView imageView, Uri uri) {
|
||||
BitmapLoader bitmapLoaderAsyncTask = getBitmapLoader(imageView);
|
||||
|
||||
if (bitmapLoaderAsyncTask != null) {
|
||||
if (bitmapLoaderAsyncTask.getUri().equals(uri)) {
|
||||
// same bitmap loader is already in progress, don't cancel
|
||||
return false;
|
||||
} else {
|
||||
// cancel previous bitmap loader
|
||||
bitmapLoaderAsyncTask.cancel(true);
|
||||
}
|
||||
}
|
||||
// imageview has no bitmap loader, or an existing bitmap loader is cancelled
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
import com.google.android.apps.mytracks.fragments.ExportDialogFragment.ExportType;
|
||||
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
|
||||
import com.google.android.gms.location.DetectedActivity;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
/**
|
||||
* Utilities to access preferences stored in {@link SharedPreferences}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class PreferencesUtils {
|
||||
|
||||
public static final int ACTIVITY_RECOGNITION_TYPE_DEFAULT = DetectedActivity.UNKNOWN;
|
||||
|
||||
/*
|
||||
* Preferences values. The defaults need to match the defaults in the xml
|
||||
* files.
|
||||
*/
|
||||
public static final boolean ALLOW_ACCESS_DEFAULT = false;
|
||||
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
|
||||
|
||||
// Values for auto_resume_track_timeout_key
|
||||
public static final int AUTO_RESUME_TRACK_TIMEOUT_ALWAYS = -1;
|
||||
public static final int AUTO_RESUME_TRACK_TIMEOUT_DEFAULT = 10;
|
||||
public static final int AUTO_RESUME_TRACK_TIMEOUT_NEVER = 0;
|
||||
|
||||
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
|
||||
|
||||
public static final boolean CHART_SHOW_CADENCE_DEFAULT = true;
|
||||
public static final boolean CHART_SHOW_ELEVATION_DEFAULT = true;
|
||||
public static final boolean CHART_SHOW_HEART_RATE_DEFAULT = true;
|
||||
public static final boolean CHART_SHOW_POWER_DEFAULT = true;
|
||||
public static final boolean CHART_SHOW_SPEED_DEFAULT = true;
|
||||
public static final String CHART_X_AXIS_DEFAULT = "DISTANCE";
|
||||
|
||||
public static final boolean CONFIRM_PLAY_EARTH_DEFAULT = true;
|
||||
|
||||
public static final String DEFAULT_ACTIVITY_DEFAULT = "";
|
||||
|
||||
public static final String DRIVE_DELETED_LIST_DEFAULT = "";
|
||||
public static final long DRIVE_LARGEST_CHANGE_ID_DEFAULT = -1L;
|
||||
public static final boolean DRIVE_SYNC_DEFAULT = false;
|
||||
|
||||
public static final String EXPORT_EXTERNAL_STORAGE_FORMAT_DEFAULT = TrackFileFormat.KML.name();
|
||||
public static final boolean EXPORT_GOOGLE_FUSION_TABLES_PUBLIC_DEFAULT = false;
|
||||
public static final boolean EXPORT_GOOGLE_MAPS_PUBLIC_DEFAULT = false;
|
||||
public static final String EXPORT_TYPE_DEFAULT = ExportType.GOOGLE_MAPS.name();
|
||||
|
||||
// Value for split_frequency_key and voice_frequency_key
|
||||
public static final int FREQUENCY_OFF = 0;
|
||||
|
||||
public static final String GOOGLE_ACCOUNT_DEFAULT = "";
|
||||
public static final int MAP_TYPE_DEFAUlT = 1;
|
||||
public static final int MAX_RECORDING_DISTANCE_DEFAULT = 200;
|
||||
|
||||
// Values for min_recording_interval_key
|
||||
public static final int MIN_RECORDING_INTERVAL_ADAPT_ACCURACY = -1;
|
||||
public static final int MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE = -2;
|
||||
public static final int MIN_RECORDING_INTERVAL_DEFAULT = 0;
|
||||
|
||||
public static final int RECORDING_DISTANCE_INTERVAL_DEFAULT = 10;
|
||||
|
||||
// Values for recording_gps_accuracy
|
||||
public static final int RECORDING_GPS_ACCURACY_DEFAULT = 50;
|
||||
public static final int RECORDING_GPS_ACCURACY_EXCELLENT = 10;
|
||||
public static final int RECORDING_GPS_ACCURACY_POOR = 2000;
|
||||
|
||||
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
|
||||
public static final boolean RECORDING_TRACK_PAUSED_DEFAULT = true;
|
||||
public static final long SELECTED_TRACK_ID_DEFAULT = -1L;
|
||||
public static final String SENSOR_TYPE_DEFAULT = "NONE";
|
||||
|
||||
// Share track
|
||||
public static final boolean SHARE_TRACK_INVITE_DEFAULT = false;
|
||||
public static final boolean SHARE_TRACK_PUBLIC_DEFAULT = false;
|
||||
|
||||
public static final int SPLIT_FREQUENCY_DEFAULT = 0;
|
||||
|
||||
// Stats
|
||||
public static final String STATS_RATE_DEFAULT = "SPEED";
|
||||
public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
|
||||
public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false;
|
||||
public static final String STATS_UNITS_DEFAULT = "METRIC";
|
||||
public static final boolean STATS_SHOW_CALORIE_DEFAULT = false;
|
||||
public static final int STATS_WEIGHT_DEFAULT = 65; // in kilogram
|
||||
|
||||
// Track color
|
||||
public static final String TRACK_COLOR_MODE_DEFAULT = "SINGLE";
|
||||
public static final int TRACK_COLOR_MODE_MEDIUM_DEFAULT = 15;
|
||||
public static final int TRACK_COLOR_MODE_PERCENTAGE_DEFAULT = 25;
|
||||
public static final int TRACK_COLOR_MODE_SLOW_DEFAULT = 9;
|
||||
|
||||
public static final String TRACK_NAME_DEFAULT = "LOCATION";
|
||||
|
||||
// Track widget
|
||||
public static final int TRACK_WIDGET_ITEM1_DEFAULT = 3; // moving time
|
||||
public static final int TRACK_WIDGET_ITEM2_DEFAULT = 0; // distance
|
||||
public static final int TRACK_WIDGET_ITEM3_DEFAULT = 1; // total time
|
||||
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
|
||||
public static final int VOICE_FREQUENCY_DEFAULT = 0;
|
||||
|
||||
private PreferencesUtils() {}
|
||||
|
||||
/**
|
||||
* Gets a preference key
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
*/
|
||||
public static String getKey(Context context, int keyId) {
|
||||
return context.getString(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
public static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a boolean preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static void setBoolean(Context context, int keyId, boolean value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putBoolean(getKey(context, keyId), value);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an integer preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
public static int getInt(Context context, int keyId, int defaultValue) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an integer preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static void setInt(Context context, int keyId, int value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(getKey(context, keyId), value);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a long preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
*/
|
||||
public static long getLong(Context context, int keyId) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getLong(getKey(context, keyId), -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a long preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static void setLong(Context context, int keyId, long value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putLong(getKey(context, keyId), value);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param defaultValue default value
|
||||
*/
|
||||
public static String getString(Context context, int keyId, String defaultValue) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a string preference value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param keyId the key id
|
||||
* @param value the value
|
||||
*/
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static void setString(Context context, int keyId, String value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(
|
||||
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putString(getKey(context, keyId), value);
|
||||
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if metric units.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isMetricUnits(Context context) {
|
||||
return PreferencesUtils.STATS_UNITS_DEFAULT.equals(
|
||||
getString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the preferred rate is speed, false if the preferred rate is
|
||||
* pace.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isReportSpeed(Context context) {
|
||||
return PreferencesUtils.STATS_RATE_DEFAULT.equals(
|
||||
getString(context, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if chart x axis is by distance, false if by time.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isChartByDistance(Context context) {
|
||||
return PreferencesUtils.CHART_X_AXIS_DEFAULT.equals(
|
||||
getString(context, R.string.chart_x_axis_key, PreferencesUtils.CHART_X_AXIS_DEFAULT));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.location.Location;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities for updating the statistics UI labels and values.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class StatsUtils {
|
||||
|
||||
private static final String COORDINATE_DEGREE = "\u00B0";
|
||||
private static final String GRADE_PERCENTAGE = "%";
|
||||
|
||||
private static final String ELEVATION_FORMAT = "%1$.2f";
|
||||
private static final String GRADE_FORMAT = "%1$d";
|
||||
private static final String CALORIES_FORMAT = "%1$.0f";
|
||||
|
||||
private StatsUtils() {}
|
||||
|
||||
/**
|
||||
* Sets the location values.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param location the location
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
public static void setLocationValues(Activity activity, Location location, boolean isRecording) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
|
||||
|
||||
// Set speed/pace
|
||||
double speed = isRecording && location != null && location.hasSpeed() ? location.getSpeed()
|
||||
: Double.NaN;
|
||||
setSpeed(activity, R.id.stats_speed, R.string.stats_speed, R.string.stats_pace, speed,
|
||||
metricUnits, reportSpeed);
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = PreferencesUtils.getBoolean(activity,
|
||||
R.string.stats_show_grade_elevation_key,
|
||||
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
|
||||
View elevation = activity.findViewById(R.id.stats_elevation);
|
||||
|
||||
if (showGradeElevation && isRecording) {
|
||||
double altitude = location != null && location.hasAltitude() ? location.getAltitude()
|
||||
: Double.NaN;
|
||||
elevation.setVisibility(View.VISIBLE);
|
||||
setElevationValue(activity, R.id.stats_elevation, -1, altitude, metricUnits);
|
||||
} else {
|
||||
elevation.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = PreferencesUtils.getBoolean(activity,
|
||||
R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
|
||||
View coordinateHorizontalLine = activity.findViewById(R.id.stats_coordinate_horizontal_line);
|
||||
View coordinateContainer = activity.findViewById(R.id.stats_coordinate_container);
|
||||
|
||||
if (showCoordinate && isRecording) {
|
||||
double latitude = location != null ? location.getLatitude() : Double.NaN;
|
||||
double longitude = location != null ? location.getLongitude() : Double.NaN;
|
||||
coordinateHorizontalLine.setVisibility(View.VISIBLE);
|
||||
coordinateContainer.setVisibility(View.VISIBLE);
|
||||
setCoordinateValue(activity, R.id.stats_latitude, R.string.stats_latitude, latitude);
|
||||
setCoordinateValue(activity, R.id.stats_longitude, R.string.stats_longitude, longitude);
|
||||
} else {
|
||||
coordinateHorizontalLine.setVisibility(View.GONE);
|
||||
coordinateContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total time value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param totalTime the total time
|
||||
*/
|
||||
public static void setTotalTimeValue(Activity activity, long totalTime) {
|
||||
setTimeValue(activity, R.id.stats_total_time, R.string.stats_total_time, totalTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the trip statistics values.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param tripStatistics the trip statistics
|
||||
*/
|
||||
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
|
||||
|
||||
// Set total distance
|
||||
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
|
||||
setDistanceValue(activity, totalDistance, metricUnits);
|
||||
|
||||
// Set total time
|
||||
setTimeValue(activity, R.id.stats_total_time, R.string.stats_total_time,
|
||||
tripStatistics != null ? tripStatistics.getTotalTime() : -1L);
|
||||
|
||||
// Set average speed/pace
|
||||
double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
|
||||
setSpeed(activity, R.id.stats_average_speed, R.string.stats_average_speed,
|
||||
R.string.stats_average_pace, averageSpeed, metricUnits, reportSpeed);
|
||||
|
||||
// Set moving time
|
||||
setTimeValue(activity, R.id.stats_moving_time, R.string.stats_moving_time,
|
||||
tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
|
||||
|
||||
// Set average moving speed/pace
|
||||
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
|
||||
: Double.NaN;
|
||||
setSpeed(activity, R.id.stats_average_moving_time, R.string.stats_average_moving_speed,
|
||||
R.string.stats_average_moving_pace, averageMovingSpeed, metricUnits, reportSpeed);
|
||||
|
||||
// Set max speed/pace
|
||||
double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
|
||||
setSpeed(activity, R.id.stats_max_speed, R.string.stats_max_speed, R.string.stats_fastest_pace,
|
||||
maxSpeed, metricUnits, reportSpeed);
|
||||
|
||||
// Set grade/elevation
|
||||
boolean showGradeElevation = PreferencesUtils.getBoolean(activity,
|
||||
R.string.stats_show_grade_elevation_key,
|
||||
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
|
||||
View gradeElevationHorizontalLine = activity.findViewById(
|
||||
R.id.stats_grade_elevation_horizontal_line);
|
||||
View gradeElevationContainer = activity.findViewById(R.id.stats_grade_elevation_container);
|
||||
|
||||
if (showGradeElevation) {
|
||||
gradeElevationHorizontalLine.setVisibility(View.VISIBLE);
|
||||
gradeElevationContainer.setVisibility(View.VISIBLE);
|
||||
// Set grade
|
||||
double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade();
|
||||
double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade();
|
||||
setGradeValue(activity, R.id.stats_grade_min, R.string.stats_min, minGrade);
|
||||
setGradeValue(activity, R.id.stats_grade_max, R.string.stats_max, maxGrade);
|
||||
|
||||
// Set elevation
|
||||
double elevationGain = tripStatistics == null ? Double.NaN
|
||||
: tripStatistics.getTotalElevationGain();
|
||||
double minElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation();
|
||||
double maxElevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation();
|
||||
setElevationValue(
|
||||
activity, R.id.stats_elevation_gain, R.string.stats_gain, elevationGain, metricUnits);
|
||||
setElevationValue(
|
||||
activity, R.id.stats_elevation_min, R.string.stats_min, minElevation, metricUnits);
|
||||
setElevationValue(
|
||||
activity, R.id.stats_elevation_max, R.string.stats_max, maxElevation, metricUnits);
|
||||
} else {
|
||||
gradeElevationHorizontalLine.setVisibility(View.GONE);
|
||||
gradeElevationContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Set calories
|
||||
boolean showCalorie = PreferencesUtils.getBoolean(activity, R.string.stats_show_calorie_key,
|
||||
PreferencesUtils.STATS_SHOW_CALORIE_DEFAULT);
|
||||
View calorieHorizontalLine = activity.findViewById(R.id.stats_calorie_horizontal_line);
|
||||
View calorieContainer = activity.findViewById(R.id.stats_calorie_container);
|
||||
|
||||
if (showCalorie) {
|
||||
calorieHorizontalLine.setVisibility(View.VISIBLE);
|
||||
calorieContainer.setVisibility(View.VISIBLE);
|
||||
double calories = tripStatistics == null ? Double.NaN : tripStatistics.getCalorie();
|
||||
setCalorie(activity, R.id.stats_calorie, R.string.stats_calorie, calories);
|
||||
} else {
|
||||
calorieHorizontalLine.setVisibility(View.GONE);
|
||||
calorieContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets speed.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param speedLabelId the speed label id
|
||||
* @param paceLabelId the pace label id
|
||||
* @param speed the speed in meters per second
|
||||
* @param metricUnits true if metric units
|
||||
* @param reportSpeed true if report speed
|
||||
*/
|
||||
private static void setSpeed(Activity activity, int itemId, int speedLabelId, int paceLabelId,
|
||||
double speed, boolean metricUnits, boolean reportSpeed) {
|
||||
String parts[] = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
setItem(activity, itemId, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets distance value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param distance the distance in meters
|
||||
* @param metricUnits true if metric units
|
||||
*/
|
||||
private static void setDistanceValue(Activity activity, double distance, boolean metricUnits) {
|
||||
String parts[] = StringUtils.getDistanceParts(activity, distance, metricUnits);
|
||||
setItem(activity, R.id.stats_distance, R.string.stats_distance, parts[0], parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a time value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the label id
|
||||
* @param time the time
|
||||
*/
|
||||
private static void setTimeValue(Activity activity, int itemId, int labelId, long time) {
|
||||
String value = time == -1L ? activity.getString(R.string.value_unknown)
|
||||
: StringUtils.formatElapsedTime(time);
|
||||
setItem(activity, itemId, labelId, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an elevation value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the label id
|
||||
* @param elevation the elevation in meters
|
||||
* @param metricUnits true if metric units
|
||||
*/
|
||||
private static void setElevationValue(
|
||||
Activity activity, int itemId, int labelId, double elevation, boolean metricUnits) {
|
||||
String value;
|
||||
String unit;
|
||||
if (Double.isNaN(elevation) || Double.isInfinite(elevation)) {
|
||||
value = null;
|
||||
unit = null;
|
||||
} else {
|
||||
if (metricUnits) {
|
||||
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
|
||||
unit = activity.getString(R.string.unit_meter);
|
||||
} else {
|
||||
elevation *= UnitConversions.M_TO_FT;
|
||||
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
|
||||
unit = activity.getString(R.string.unit_feet);
|
||||
}
|
||||
}
|
||||
setItem(activity, itemId, labelId, value, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a grade value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the label id
|
||||
* @param grade the grade in fraction between 0 and 1
|
||||
*/
|
||||
private static void setGradeValue(Activity activity, int itemId, int labelId, double grade) {
|
||||
String value = Double.isNaN(grade) || Double.isInfinite(grade) ? null
|
||||
: String.format(Locale.getDefault(), GRADE_FORMAT, Math.round(grade * 100));
|
||||
setItem(activity, itemId, labelId, value, GRADE_PERCENTAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a coordinate value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the label id
|
||||
* @param coordinate the coordinate in degrees
|
||||
*/
|
||||
private static void setCoordinateValue(
|
||||
Activity activity, int itemId, int labelId, double coordinate) {
|
||||
String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null
|
||||
: Location.convert(coordinate, Location.FORMAT_DEGREES);
|
||||
setItem(activity, itemId, labelId, value, COORDINATE_DEGREE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the label id. -1 to hide the label
|
||||
* @param value the value, can be null
|
||||
* @param unit the unit. Null to hide the unit
|
||||
*/
|
||||
private static void setItem(
|
||||
Activity activity, int itemId, int labelId, String value, String unit) {
|
||||
View view = activity.findViewById(itemId);
|
||||
TextView labelTextView = (TextView) view.findViewById(R.id.stats_label);
|
||||
TextView valueTextView = (TextView) view.findViewById(R.id.stats_value);
|
||||
TextView unitTextView = (TextView) view.findViewById(R.id.stats_unit);
|
||||
if (labelTextView == null || valueTextView == null || unitTextView == null) {
|
||||
return;
|
||||
}
|
||||
if (labelId == -1) {
|
||||
labelTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
labelTextView.setVisibility(View.VISIBLE);
|
||||
labelTextView.setText(labelId);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
value = activity.getString(R.string.value_unknown);
|
||||
unitTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (unit == null) {
|
||||
unitTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
unitTextView.setVisibility(View.VISIBLE);
|
||||
unitTextView.setText(unit);
|
||||
}
|
||||
}
|
||||
valueTextView.setText(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets calorie.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param itemId the item id
|
||||
* @param labelId the calorie label id
|
||||
* @param calorie the value of calorie
|
||||
*/
|
||||
private static void setCalorie(Activity activity, int itemId, int labelId, double calorie) {
|
||||
setItem(activity, itemId, labelId,
|
||||
String.format(Locale.getDefault(), CALORIES_FORMAT, calorie), activity.getString(R.string.unit_calorie));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 2008 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.util;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Various string manipulation methods.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StringUtils {
|
||||
|
||||
private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||
private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss", Locale.US);
|
||||
private static final Pattern ISO_8601_EXTRAS = Pattern.compile(
|
||||
"^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
|
||||
static {
|
||||
ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private StringUtils() {}
|
||||
|
||||
/**
|
||||
* Formats the date and time based on user's phone date/time preferences.
|
||||
*
|
||||
* @param context the context
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatDateTime(Context context, long time) {
|
||||
return DateUtils.formatDateTime(
|
||||
context, time, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE) + " "
|
||||
+ DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the time using the ISO 8601 date time format with fractional
|
||||
* seconds in UTC time zone.
|
||||
*
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatDateTimeIso8601(long time) {
|
||||
return ISO_8601_DATE_TIME_FORMAT.format(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the elapsed timed in the form "MM:SS" or "H:MM:SS".
|
||||
*
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatElapsedTime(long time) {
|
||||
/*
|
||||
* Temporary workaround for DateUtils.formatElapsedTime(time / 1000). In API
|
||||
* level 17, it returns strings like "1:0:00" instead of "1:00:00", which
|
||||
* breaks several unit tests.
|
||||
*/
|
||||
if (time < 0) {
|
||||
return "-";
|
||||
}
|
||||
long hours = 0;
|
||||
long minutes = 0;
|
||||
long seconds = 0;
|
||||
long elapsedSeconds = time / 1000;
|
||||
|
||||
if (elapsedSeconds >= 3600) {
|
||||
hours = elapsedSeconds / 3600;
|
||||
elapsedSeconds -= hours * 3600;
|
||||
}
|
||||
if (elapsedSeconds >= 60) {
|
||||
minutes = elapsedSeconds / 60;
|
||||
elapsedSeconds -= minutes * 60;
|
||||
}
|
||||
seconds = elapsedSeconds;
|
||||
|
||||
if (hours > 0) {
|
||||
return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
|
||||
} else {
|
||||
return String.format(Locale.US, "%02d:%02d", minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the elapsed time in the form "H:MM:SS".
|
||||
*
|
||||
* @param time the time in milliseconds
|
||||
*/
|
||||
public static String formatElapsedTimeWithHour(long time) {
|
||||
String value = formatElapsedTime(time);
|
||||
return TextUtils.split(value, ":").length == 2 ? "0:" + value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the distance.
|
||||
*
|
||||
* @param context the context
|
||||
* @param distance the distance in meters
|
||||
* @param metricUnits true to use metric units. False to use imperial units
|
||||
*/
|
||||
public static String formatDistance(Context context, double distance, boolean metricUnits) {
|
||||
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
|
||||
return context.getString(R.string.value_unknown);
|
||||
}
|
||||
if (metricUnits) {
|
||||
if (distance > 500.0) {
|
||||
distance *= UnitConversions.M_TO_KM;
|
||||
return context.getString(R.string.value_float_kilometer, distance);
|
||||
} else {
|
||||
return context.getString(R.string.value_float_meter, distance);
|
||||
}
|
||||
} else {
|
||||
if (distance * UnitConversions.M_TO_MI > 0.5) {
|
||||
distance *= UnitConversions.M_TO_MI;
|
||||
return context.getString(R.string.value_float_mile, distance);
|
||||
} else {
|
||||
distance *= UnitConversions.M_TO_FT;
|
||||
return context.getString(R.string.value_float_feet, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance in an array of two strings. The first string is the
|
||||
* distance. The second string is the unit. The first string is null if the
|
||||
* distance is invalid.
|
||||
*
|
||||
* @param context the context
|
||||
* @param distance the distance
|
||||
* @param metricUnits true to use metric unit
|
||||
*/
|
||||
public static String[] getDistanceParts(Context context, double distance, boolean metricUnits) {
|
||||
String[] result = new String[2];
|
||||
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
|
||||
result[0] = null;
|
||||
result[1] = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||
return result;
|
||||
}
|
||||
|
||||
int unitId;
|
||||
if (metricUnits) {
|
||||
if (distance > 500.0) {
|
||||
distance *= UnitConversions.M_TO_KM;
|
||||
unitId = R.string.unit_kilometer;
|
||||
} else {
|
||||
unitId = R.string.unit_meter;
|
||||
}
|
||||
} else {
|
||||
if (distance * UnitConversions.M_TO_MI > 0.5) {
|
||||
distance *= UnitConversions.M_TO_MI;
|
||||
unitId = R.string.unit_mile;
|
||||
} else {
|
||||
distance *= UnitConversions.M_TO_FT;
|
||||
unitId = R.string.unit_feet;
|
||||
}
|
||||
}
|
||||
result[0] = String.format("%.2f", distance);
|
||||
result[1] = context.getString(unitId);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the speed in an array of two strings. The first string is the speed.
|
||||
* The second string is the unit. The first string is null if speed is
|
||||
* invalid.
|
||||
*
|
||||
* @param context the context
|
||||
* @param speed the speed
|
||||
* @param metricUnits true to use metric unit
|
||||
* @param reportSpeed true to report speed
|
||||
*/
|
||||
public static String[] getSpeedParts(
|
||||
Context context, double speed, boolean metricUnits, boolean reportSpeed) {
|
||||
String[] result = new String[2];
|
||||
int unitId;
|
||||
if (metricUnits) {
|
||||
unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||
} else {
|
||||
unitId = reportSpeed ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||
}
|
||||
result[1] = context.getString(unitId);
|
||||
if (Double.isNaN(speed) || Double.isInfinite(speed)) {
|
||||
result[0] = null;
|
||||
return result;
|
||||
}
|
||||
speed *= UnitConversions.MS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
if (reportSpeed) {
|
||||
result[0] = String.format("%.2f", speed);
|
||||
} else {
|
||||
// convert from hours to minutes
|
||||
double pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
int minutes = (int) pace;
|
||||
int seconds = (int) Math.round((pace - minutes) * 60.0);
|
||||
result[0] = String.format("%d:%02d", minutes, seconds);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string for category and description.
|
||||
*
|
||||
* @param category the category
|
||||
* @param description the description
|
||||
*/
|
||||
public static String getCategoryDescription(String category, String description) {
|
||||
if (category == null || category.length() == 0) {
|
||||
return description;
|
||||
}
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
buffer.append("[" + category + "]");
|
||||
if (description != null && description.length() != 0) {
|
||||
buffer.append(" " + description);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given text as a XML CDATA element. This includes adding the
|
||||
* starting and ending CDATA tags. Please notice that this may result in
|
||||
* multiple consecutive CDATA tags.
|
||||
*
|
||||
* @param text the given text
|
||||
*/
|
||||
public static String formatCData(String text) {
|
||||
return "<![CDATA[" + text.replaceAll("]]>", "]]]]><![CDATA[>") + "]]>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time, in milliseconds, from an XML date time string as defined at
|
||||
* http://www.w3.org/TR/xmlschema-2/#dateTime
|
||||
*
|
||||
* @param xmlDateTime the XML date time string
|
||||
*/
|
||||
public static long getTime(String xmlDateTime) {
|
||||
// Parse the date time base
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
Date date = ISO_8601_BASE.parse(xmlDateTime, position);
|
||||
if (date == null) {
|
||||
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime
|
||||
+ " (at position " + position.getErrorIndex() + ")");
|
||||
}
|
||||
|
||||
// Parse the date time extras
|
||||
Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex()));
|
||||
if (!matcher.matches()) {
|
||||
// This will match even an empty string as all groups are optional. Thus a
|
||||
// non-match means invalid content.
|
||||
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime);
|
||||
}
|
||||
|
||||
long time = date.getTime();
|
||||
|
||||
// Account for fractional seconds
|
||||
String fractional = matcher.group(1);
|
||||
if (fractional != null) {
|
||||
// Regex ensures fractional part is in (0,1)
|
||||
float fractionalSeconds = Float.parseFloat(fractional);
|
||||
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
|
||||
time += fractionalMillis;
|
||||
}
|
||||
|
||||
// Account for timezones
|
||||
String sign = matcher.group(2);
|
||||
String offsetHoursStr = matcher.group(3);
|
||||
String offsetMinsStr = matcher.group(4);
|
||||
if (sign != null && offsetHoursStr != null && offsetMinsStr != null) {
|
||||
// Regex ensures sign is + or -
|
||||
boolean plusSign = sign.equals("+");
|
||||
int offsetHours = Integer.parseInt(offsetHoursStr);
|
||||
int offsetMins = Integer.parseInt(offsetMinsStr);
|
||||
|
||||
// Regex ensures values are >= 0
|
||||
if (offsetHours > 14 || offsetMins > 59) {
|
||||
throw new IllegalArgumentException("Bad timezone: " + xmlDateTime);
|
||||
}
|
||||
|
||||
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
|
||||
|
||||
// Convert to UTC
|
||||
if (plusSign) {
|
||||
time -= totalOffsetMillis;
|
||||
} else {
|
||||
time += totalOffsetMillis;
|
||||
}
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time as an array of three integers. Index 0 contains the number of
|
||||
* seconds, index 1 contains the number of minutes, and index 2 contains the
|
||||
* number of hours.
|
||||
*
|
||||
* @param time the time in milliseconds
|
||||
* @return an array of 3 elements.
|
||||
*/
|
||||
public static int[] getTimeParts(long time) {
|
||||
if (time < 0) {
|
||||
int[] parts = getTimeParts(time * -1);
|
||||
parts[0] *= -1;
|
||||
parts[1] *= -1;
|
||||
parts[2] *= -1;
|
||||
return parts;
|
||||
}
|
||||
int[] parts = new int[3];
|
||||
|
||||
long seconds = time / 1000;
|
||||
parts[0] = (int) (seconds % 60);
|
||||
int minutes = (int) (seconds / 60);
|
||||
parts[1] = minutes % 60;
|
||||
parts[2] = minutes / 60;
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the html.
|
||||
*
|
||||
* @param context the context
|
||||
* @param resId the string resource id
|
||||
* @param formatArgs the string resource ids of the format arguments
|
||||
*/
|
||||
public static Spanned getHtml(Context context, int resId, Object... formatArgs) {
|
||||
Object[] args = new Object[formatArgs.length];
|
||||
for (int i = 0; i < formatArgs.length; i++) {
|
||||
String url = context.getString((Integer) formatArgs[i]);
|
||||
args[i] = " <a href='" + url + "'>" + url + "</a> ";
|
||||
}
|
||||
return Html.fromHtml(context.getString(resId, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the frequency display options.
|
||||
*
|
||||
* @param context the context
|
||||
* @param metricUnits true to display in metric units
|
||||
*/
|
||||
public static String[] getFrequencyOptions(Context context, boolean metricUnits) {
|
||||
String[] values = context.getResources().getStringArray(R.array.frequency_values);
|
||||
String[] options = new String[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
int value = Integer.parseInt(values[i]);
|
||||
if (value == PreferencesUtils.FREQUENCY_OFF) {
|
||||
options[i] = context.getString(R.string.value_off);
|
||||
} else if (value < 0) {
|
||||
options[i] = context.getString(
|
||||
metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile,
|
||||
Math.abs(value));
|
||||
} else {
|
||||
options[i] = context.getString(R.string.value_integer_minute, value);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2011 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.Constants;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Utility class for accessing basic Android functionality.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SystemUtils {
|
||||
|
||||
private static final String TAG = SystemUtils.class.getSimpleName();
|
||||
|
||||
private SystemUtils() {}
|
||||
|
||||
/**
|
||||
* Get the My Tracks version from the manifest.
|
||||
*
|
||||
* @return the version, or an empty string in case of failure.
|
||||
*/
|
||||
public static String getMyTracksVersion(Context context) {
|
||||
try {
|
||||
PackageInfo pi = context.getPackageManager()
|
||||
.getPackageInfo("com.google.android.maps.mytracks", PackageManager.GET_META_DATA);
|
||||
return pi.versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(Constants.TAG, "Failed to get version info.", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to acquire a partial wake lock if not already acquired. Logs errors
|
||||
* and gives up trying in case the wake lock cannot be acquired.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Acquire a wake lock if not already acquired.
|
||||
*
|
||||
* @param context the context
|
||||
* @param wakeLock wake lock or null
|
||||
*/
|
||||
@SuppressLint("Wakelock")
|
||||
public static WakeLock acquireWakeLock(Context context, WakeLock wakeLock) {
|
||||
Log.i(TAG, "Acquiring wake lock.");
|
||||
try {
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
if (powerManager == null) {
|
||||
Log.e(TAG, "Power manager null.");
|
||||
return wakeLock;
|
||||
}
|
||||
if (wakeLock == null) {
|
||||
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
|
||||
if (wakeLock == null) {
|
||||
Log.e(TAG, "Cannot create a new wake lock.");
|
||||
}
|
||||
return wakeLock;
|
||||
}
|
||||
if (!wakeLock.isHeld()) {
|
||||
wakeLock.acquire();
|
||||
if (!wakeLock.isHeld()) {
|
||||
Log.e(TAG, "Cannot acquire wake lock.");
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
return wakeLock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Color;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for track icon.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TrackIconUtils {
|
||||
|
||||
private static final String AIRPLANE = "AIRPLANE";
|
||||
public static final String BIKE = "BIKE";
|
||||
private static final String BOAT = "BOAT";
|
||||
public static final String DRIVE = "DRIVE";
|
||||
private static final String RUN = "RUN";
|
||||
private static final String SKI = "SKI";
|
||||
private static final String SNOW_BOARDING = "SNOW_BOARDING";
|
||||
public static final String WALK = "WALK";
|
||||
|
||||
private TrackIconUtils() {}
|
||||
|
||||
private static LinkedHashMap<String, Pair<Integer, Integer>> map =
|
||||
new LinkedHashMap<String, Pair<Integer, Integer>>();
|
||||
|
||||
static {
|
||||
map.put(
|
||||
RUN, new Pair<Integer, Integer>(R.string.activity_type_running, R.drawable.ic_track_run));
|
||||
map.put(
|
||||
WALK, new Pair<Integer, Integer>(R.string.activity_type_walking, R.drawable.ic_track_walk));
|
||||
map.put(
|
||||
BIKE, new Pair<Integer, Integer>(R.string.activity_type_biking, R.drawable.ic_track_bike));
|
||||
map.put(DRIVE,
|
||||
new Pair<Integer, Integer>(R.string.activity_type_driving, R.drawable.ic_track_drive));
|
||||
map.put(
|
||||
SKI, new Pair<Integer, Integer>(R.string.activity_type_skiing, R.drawable.ic_track_ski));
|
||||
map.put(SNOW_BOARDING, new Pair<Integer, Integer>(
|
||||
R.string.activity_type_snow_boarding, R.drawable.ic_track_snow_boarding));
|
||||
map.put(AIRPLANE,
|
||||
new Pair<Integer, Integer>(R.string.activity_type_airplane, R.drawable.ic_track_airplane));
|
||||
map.put(
|
||||
BOAT, new Pair<Integer, Integer>(R.string.activity_type_boat, R.drawable.ic_track_boat));
|
||||
}
|
||||
|
||||
private static int[] airplane = new int[] {
|
||||
R.string.activity_type_airplane, R.string.activity_type_commercial_airplane,
|
||||
R.string.activity_type_rc_airplane};
|
||||
private static int[] bike = new int[] {R.string.activity_type_biking,
|
||||
R.string.activity_type_cycling,
|
||||
R.string.activity_type_dirt_bike,
|
||||
R.string.activity_type_motor_bike,
|
||||
R.string.activity_type_mountain_biking,
|
||||
R.string.activity_type_road_biking,
|
||||
R.string.activity_type_track_cycling};
|
||||
private static int[] boat = new int[] {R.string.activity_type_boat, R.string.activity_type_ferry,
|
||||
R.string.activity_type_motor_boating, R.string.activity_type_rc_boat};
|
||||
private static int[] drive = new int[] {
|
||||
R.string.activity_type_atv, R.string.activity_type_driving,
|
||||
R.string.activity_type_driving_bus, R.string.activity_type_driving_car};
|
||||
private static int[] run = new int[] {
|
||||
R.string.activity_type_running, R.string.activity_type_street_running,
|
||||
R.string.activity_type_track_running, R.string.activity_type_trail_running};
|
||||
private static int[] ski =
|
||||
new int[] {R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing};
|
||||
private static int[] snowBoarding = new int[] {R.string.activity_type_snow_boarding};
|
||||
private static int[] walk = new int[] {R.string.activity_type_hiking,
|
||||
R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking,
|
||||
R.string.activity_type_trail_hiking, R.string.activity_type_walking};
|
||||
|
||||
/**
|
||||
* Gets the icon drawable.
|
||||
*
|
||||
* @param iconValue the icon value
|
||||
*/
|
||||
public static int getIconDrawable(String iconValue) {
|
||||
if (iconValue == null || iconValue.equals("")) {
|
||||
return R.drawable.ic_track_walk;
|
||||
}
|
||||
Pair<Integer, Integer> pair = map.get(iconValue);
|
||||
return pair == null ? R.drawable.ic_track_walk : pair.second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon activity type.
|
||||
*
|
||||
* @param iconValue the icon value
|
||||
*/
|
||||
public static int getIconActivityType(String iconValue) {
|
||||
if (iconValue == null || iconValue.equals("")) {
|
||||
return R.string.activity_type_walking;
|
||||
}
|
||||
Pair<Integer, Integer> pair = map.get(iconValue);
|
||||
return pair == null ? R.string.activity_type_walking : pair.first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all icon values.
|
||||
*/
|
||||
public static List<String> getAllIconValues() {
|
||||
List<String> values = new ArrayList<String>();
|
||||
for (String value : map.keySet()) {
|
||||
values.add(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the icon value.
|
||||
*
|
||||
* @param context the context
|
||||
* @param activityType the activity type
|
||||
*/
|
||||
public static String getIconValue(Context context, String activityType) {
|
||||
if (inList(context, activityType, airplane)) {
|
||||
return AIRPLANE;
|
||||
}
|
||||
if (inList(context, activityType, bike)) {
|
||||
return BIKE;
|
||||
}
|
||||
if (inList(context, activityType, boat)) {
|
||||
return BOAT;
|
||||
}
|
||||
if (inList(context, activityType, drive)) {
|
||||
return DRIVE;
|
||||
}
|
||||
if (inList(context, activityType, run)) {
|
||||
return RUN;
|
||||
}
|
||||
if (inList(context, activityType, ski)) {
|
||||
return SKI;
|
||||
}
|
||||
if (inList(context, activityType, snowBoarding)) {
|
||||
return SNOW_BOARDING;
|
||||
}
|
||||
if (inList(context, activityType, walk)) {
|
||||
return WALK;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts the colors in a bitmap.
|
||||
*
|
||||
* @param source the source
|
||||
*/
|
||||
public static Bitmap invertBitmap(Bitmap source) {
|
||||
Config config = source.getConfig();
|
||||
if (config == null) {
|
||||
config = Config.ARGB_8888;
|
||||
}
|
||||
Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), config);
|
||||
int height = source.getHeight();
|
||||
int width = source.getWidth();
|
||||
int pixelColor;
|
||||
int alpha;
|
||||
int red;
|
||||
int green;
|
||||
int blue;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
pixelColor = source.getPixel(x, y);
|
||||
alpha = Color.alpha(pixelColor);
|
||||
red = 255 - Color.red(pixelColor);
|
||||
green = 255 - Color.green(pixelColor);
|
||||
blue = 255 - Color.blue(pixelColor);
|
||||
output.setPixel(x, y, Color.argb(alpha, red, green, blue));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the activity type is in the list.
|
||||
*
|
||||
* @param context the context
|
||||
* @param activityType the activity type
|
||||
* @param list the list
|
||||
*/
|
||||
private static boolean inList(Context context, String activityType, int[] list) {
|
||||
for (int i : list) {
|
||||
if (context.getString(i).equals(activityType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Address;
|
||||
import android.location.Geocoder;
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utilities for track name.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
public class TrackNameUtils {
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ISO_8601_FORMAT = "yyyy-MM-dd HH:mm";
|
||||
|
||||
private TrackNameUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track name.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackId the track id
|
||||
* @param startTime the track start time
|
||||
* @param location the track location
|
||||
*/
|
||||
public static String getTrackName(
|
||||
Context context, long trackId, long startTime, Location location) {
|
||||
String trackName = PreferencesUtils.getString(context, R.string.track_name_key,
|
||||
PreferencesUtils.TRACK_NAME_DEFAULT);
|
||||
|
||||
if (trackName.equals(
|
||||
context.getString(R.string.settings_recording_track_name_date_local_value))) {
|
||||
if (startTime == -1L) {
|
||||
return null;
|
||||
}
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
} else if (trackName.equals(
|
||||
context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
|
||||
if (startTime == -1L) {
|
||||
return null;
|
||||
}
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat(ISO_8601_FORMAT, Locale.US);
|
||||
return dateFormat.format(startTime);
|
||||
} else if (trackName.equals(
|
||||
context.getString(R.string.settings_recording_track_name_number_value))) {
|
||||
if (trackId == -1L) {
|
||||
return null;
|
||||
}
|
||||
return context.getString(R.string.track_name_format, trackId);
|
||||
} else {
|
||||
// trackName equals settings_recording_track_name_location_value or any
|
||||
// value
|
||||
if (location != null) {
|
||||
return getReverseGeoCoding(context, location);
|
||||
} else {
|
||||
// Use the startTime if available
|
||||
if (startTime != -1L) {
|
||||
return StringUtils.formatDateTime(context, startTime);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reverse geo coding string for a location.
|
||||
*
|
||||
* @param context the context
|
||||
* @param location the location
|
||||
*/
|
||||
private static String getReverseGeoCoding(Context context, Location location) {
|
||||
if (location == null || !ApiAdapterFactory.getApiAdapter().isGeoCoderPresent()) {
|
||||
return null;
|
||||
}
|
||||
Geocoder geocoder = new Geocoder(context);
|
||||
try {
|
||||
List<Address> addresses = geocoder.getFromLocation(
|
||||
location.getLatitude(), location.getLongitude(), 1);
|
||||
if (addresses.size() > 0) {
|
||||
Address address = addresses.get(0);
|
||||
int lines = address.getMaxAddressLineIndex();
|
||||
if (lines > 0) {
|
||||
return address.getAddressLine(0);
|
||||
}
|
||||
String featureName = address.getFeatureName();
|
||||
if (featureName != null) {
|
||||
return featureName;
|
||||
}
|
||||
String thoroughfare = address.getThoroughfare();
|
||||
if (thoroughfare != null) {
|
||||
return thoroughfare;
|
||||
}
|
||||
String locality = address.getLocality();
|
||||
if (locality != null) {
|
||||
return locality;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Can safely ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2011 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.util;
|
||||
|
||||
import com.google.android.apps.mytracks.TrackEditActivity;
|
||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningServiceInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for {@link TrackRecordingServiceConnection}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackRecordingServiceConnectionUtils {
|
||||
|
||||
private static final String TAG = TrackRecordingServiceConnectionUtils.class.getSimpleName();
|
||||
|
||||
private TrackRecordingServiceConnectionUtils() {}
|
||||
|
||||
/**
|
||||
* Returns true if the recording service is running.
|
||||
*
|
||||
* @param context the current context
|
||||
*/
|
||||
public static boolean isRecordingServiceRunning(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (RunningServiceInfo serviceInfo : services) {
|
||||
ComponentName componentName = serviceInfo.service;
|
||||
String serviceName = componentName.getClassName();
|
||||
if (TrackRecordingService.class.getName().equals(serviceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the recording track.
|
||||
*
|
||||
* @param trackRecordingServiceConnection the track recording service
|
||||
*/
|
||||
public static void resumeTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) {
|
||||
try {
|
||||
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service != null) {
|
||||
service.resumeCurrentTrack();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to resume track.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the recording track.
|
||||
*
|
||||
* @param trackRecordingServiceConnection the track recording service
|
||||
* connection
|
||||
*/
|
||||
public static void pauseTrack(TrackRecordingServiceConnection trackRecordingServiceConnection) {
|
||||
try {
|
||||
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service != null) {
|
||||
service.pauseCurrentTrack();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to resume track.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the recording.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackRecordingServiceConnection the track recording service
|
||||
* connection
|
||||
* @param showEditor true to show the editor
|
||||
*/
|
||||
public static void stopRecording(Context context,
|
||||
TrackRecordingServiceConnection trackRecordingServiceConnection, boolean showEditor) {
|
||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
|
||||
.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
try {
|
||||
if (showEditor) {
|
||||
/*
|
||||
* Need to remember the recordingTrackId before calling
|
||||
* endCurrentTrack. endCurrentTrack sets the value to -1L.
|
||||
*/
|
||||
long recordingTrackId = PreferencesUtils.getLong(
|
||||
context, R.string.recording_track_id_key);
|
||||
trackRecordingService.endCurrentTrack();
|
||||
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId)
|
||||
.putExtra(TrackEditActivity.EXTRA_NEW_TRACK, true);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
} else {
|
||||
trackRecordingService.endCurrentTrack();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to stop recording.", e);
|
||||
}
|
||||
} else {
|
||||
resetRecordingState(context);
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the track recording service connection.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackRecordingServiceConnection the track recording service
|
||||
* connection
|
||||
*/
|
||||
public static void startConnection(
|
||||
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
|
||||
trackRecordingServiceConnection.bindIfStarted();
|
||||
if (!isRecordingServiceRunning(context)) {
|
||||
resetRecordingState(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetRecordingState(Context context) {
|
||||
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
||||
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
PreferencesUtils.setLong(
|
||||
context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
boolean recordingTrackPaused = PreferencesUtils.getBoolean(context,
|
||||
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
if (!recordingTrackPaused) {
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key,
|
||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker.
|
||||
*/
|
||||
public static long addMarker(Context context,
|
||||
TrackRecordingServiceConnection trackRecordingServiceConnection,
|
||||
WaypointCreationRequest waypointCreationRequest) {
|
||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
|
||||
.getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(TAG, "Unable to add marker, no track recording service");
|
||||
} else {
|
||||
try {
|
||||
long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest);
|
||||
if (markerId != -1L) {
|
||||
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
|
||||
return markerId;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to add marker", e);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Unable to add marker.", e);
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2008 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.util;
|
||||
|
||||
/**
|
||||
* Unit conversion constants.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class UnitConversions {
|
||||
|
||||
private UnitConversions() {}
|
||||
|
||||
// multiplication factor to convert milliliters to liters
|
||||
public static final double ML_TO_L = 1 / 1000.0;
|
||||
|
||||
// multiplication factor to convert milliseconds to seconds
|
||||
public static final double MS_TO_S = 1 / 1000.0;
|
||||
|
||||
// multiplication factor to convert minutes to seconds
|
||||
public static final double MIN_TO_S = 60.0;
|
||||
|
||||
// multiplication factor to convert seconds to minutes
|
||||
public static final double S_TO_MIN = 1 / MIN_TO_S;
|
||||
|
||||
// multiplication factor to convert hours to minutes
|
||||
public static final double HR_TO_MIN = 60.0;
|
||||
|
||||
// multiplication factor to convert minutes to hours
|
||||
public static final double MIN_TO_HR = 1 / HR_TO_MIN;
|
||||
|
||||
// multiplication factor to convert kilometers to miles
|
||||
public static final double KM_TO_MI = 0.621371192;
|
||||
|
||||
// multiplication factor to convert kilograms to pounds
|
||||
public static final double KG_TO_LB = 2.2046;
|
||||
|
||||
// multiplication factor to convert pounds to kilograms
|
||||
public static final double LB_TO_KG = 1 / KG_TO_LB;
|
||||
|
||||
// multiplication factor to convert miles to kilometers
|
||||
public static final double MI_TO_KM = 1 / KM_TO_MI;
|
||||
|
||||
// multiplication factor to convert miles to feet
|
||||
public static final double MI_TO_FT = 5280.0;
|
||||
|
||||
// multiplication factor to convert feet to miles
|
||||
public static final double FT_TO_MI = 1 / MI_TO_FT;
|
||||
|
||||
// multiplication factor to convert meters to kilometers
|
||||
public static final double M_TO_KM = 1 / 1000.0;
|
||||
|
||||
public static final double KM_TO_M = 1 / M_TO_KM;
|
||||
|
||||
// multiplication factor to convert meters per second to kilometers per hour
|
||||
public static final double MS_TO_KMH = 3.6;
|
||||
|
||||
// multiplication factor to convert meters to miles
|
||||
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
|
||||
|
||||
// multiplication factor to convert meters to feet
|
||||
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
|
||||
|
||||
// multiplication factor to convert degrees to radians
|
||||
public static final double DEG_TO_RAD = Math.PI / 180.0;
|
||||
|
||||
// multiplication factor to convert Kcal to joules.
|
||||
public static final double KCAL_TO_J = 4.184;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2011 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.util;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with content and other types of URIs.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class UriUtils {
|
||||
|
||||
public static boolean matchesContentUri(Uri uri, Uri baseContentUri) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that scheme and authority are the same.
|
||||
if (!uri.getScheme().equals(baseContentUri.getScheme()) ||
|
||||
!uri.getAuthority().equals(baseContentUri.getAuthority())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks that all the base path components are in the URI.
|
||||
List<String> uriPathSegments = uri.getPathSegments();
|
||||
List<String> basePathSegments = baseContentUri.getPathSegments();
|
||||
if (basePathSegments.size() > uriPathSegments.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < basePathSegments.size(); i++) {
|
||||
if (!uriPathSegments.get(i).equals(basePathSegments.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isFileUri(Uri uri) {
|
||||
return "file".equals(uri.getScheme());
|
||||
}
|
||||
|
||||
private UriUtils() {}
|
||||
}
|
||||
Reference in New Issue
Block a user