In MapFragment, use MyTracksLocationManager directly to obtain the current location.

This commit is contained in:
Jimmy Shih
2013-05-21 14:03:48 -07:00
commit cffd72fa07
990 changed files with 73903 additions and 0 deletions
@@ -0,0 +1,83 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.TextView;
/**
* A DialogFragment to show information about My Tracks.
*
* @author Jimmy Shih
*/
public class AboutDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface AboutCaller {
/**
* Called when about license is invoked.
*/
public void onAboutLicense();
}
public static final String ABOUT_DIALOG_TAG = "aboutDialog";
private AboutCaller caller;
private FragmentActivity fragmentActivity;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (AboutCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + AboutCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
View view = fragmentActivity.getLayoutInflater().inflate(R.layout.about, null);
TextView aboutVersion = (TextView) view.findViewById(R.id.about_version);
aboutVersion.setText(SystemUtils.getMyTracksVersion(fragmentActivity));
return new AlertDialog.Builder(fragmentActivity).setNegativeButton(
R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
caller.onAboutLicense();
}
}).setPositiveButton(R.string.generic_ok, null).setTitle(R.string.help_about).setView(view)
.create();
}
}
@@ -0,0 +1,142 @@
/*
* 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.fragments;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.View;
import android.widget.FilterQueryProvider;
import android.widget.MultiAutoCompleteTextView;
/**
* A DialogFragment to add emails.
*
* @author Jimmy Shih
*/
public class AddEmailsDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface AddEmailsCaller {
/**
* Called when add emails is done.
*
* @param emails the added emails
*/
public void onAddEmailsDone(String emails);
}
public static final String ADD_EMAILS_DIALOG_TAG = "addEmailsDialog";
private static final String KEY_TRACK_ID = "trackId";
public static AddEmailsDialogFragment newInstance(long trackId) {
Bundle bundle = new Bundle();
bundle.putLong(KEY_TRACK_ID, trackId);
AddEmailsDialogFragment addPeopleDialogFragment = new AddEmailsDialogFragment();
addPeopleDialogFragment.setArguments(bundle);
return addPeopleDialogFragment;
}
private AddEmailsCaller caller;
private FragmentActivity fragmentActivity;
private MultiAutoCompleteTextView multiAutoCompleteTextView;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (AddEmailsCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + AddEmailsCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
View view = fragmentActivity.getLayoutInflater().inflate(R.layout.add_emails, null);
multiAutoCompleteTextView = (MultiAutoCompleteTextView) view.findViewById(R.id.add_emails);
multiAutoCompleteTextView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
SimpleCursorAdapter adapter = new SimpleCursorAdapter(fragmentActivity,
R.layout.add_emails_item, getCursor(fragmentActivity, null), new String[] {
ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
adapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() {
@Override
public CharSequence convertToString(Cursor cursor) {
int index = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
return cursor.getString(index).trim();
}
});
adapter.setFilterQueryProvider(new FilterQueryProvider() {
@Override
public Cursor runQuery(CharSequence constraint) {
return getCursor(fragmentActivity, constraint);
}
});
multiAutoCompleteTextView.setAdapter(adapter);
return new AlertDialog.Builder(fragmentActivity).setNegativeButton(
R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String acl = multiAutoCompleteTextView.getText().toString();
caller.onAddEmailsDone(acl);
}
}).setTitle(R.string.share_track_add_emails_title).setView(view).create();
}
/**
* Gets the cursor
*
* @param activity the activity
* @param constraint the constraint
*/
private Cursor getCursor(Activity activity, CharSequence constraint) {
String order = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '1'";
if (constraint != null) {
selection += " AND (" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE '%" + constraint
+ "%' OR " + ContactsContract.CommonDataKinds.Email.DATA + " LIKE '%" + constraint
+ "%' )";
}
String[] projection = new String[] { ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA };
Uri uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
return activity.getContentResolver().query(uri, projection, selection, null, order);
}
}
@@ -0,0 +1,540 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.ChartView;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ZoomControls;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* A fragment to display track chart to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartFragment extends Fragment implements TrackDataListener {
public static final String CHART_FRAGMENT_TAG = "chartFragment";
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
private TrackDataHub trackDataHub;
// Stats gathered from the received data
private TripStatisticsUpdater tripStatisticsUpdater;
private long startTime;
private boolean metricUnits = PreferencesUtils.METRIC_UNITS_DEFAULT;
private boolean reportSpeed = PreferencesUtils.REPORT_SPEED_DEFAULT;
private int minRecordingDistance = PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT;
// Modes of operation
private boolean chartByDistance = true;
private boolean[] chartShow = new boolean[] { true, true, true, true, true, true };
// UI elements
private ChartView chartView;
private ZoomControls zoomControls;
/**
* A runnable that will enable/disable zoom controls and orange pointer as
* appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
public void run() {
if (!isResumed() || trackDataHub == null) {
return;
}
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(isSelectedTrackRecording());
chartView.invalidate();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Create a chartView here to store data thus won't need to reload all the
* data on every onStart or onResume.
*/
chartView = new ChartView(getActivity());
};
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.chart, container, false);
zoomControls = (ZoomControls) view.findViewById(R.id.chart_zoom_controls);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomIn();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomOut();
}
});
return view;
}
@Override
public void onStart() {
super.onStart();
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.chart_view_layout);
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layout.addView(chartView, layoutParams);
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
checkChartSettings();
getActivity().runOnUiThread(updateChart);
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
@Override
public void onStop() {
super.onStop();
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.chart_view_layout);
layout.removeView(chartView);
}
@Override
public void onLocationStateChanged(LocationState state) {
// We don't care.
}
@Override
public void onLocationChanged(Location loc) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track) {
// We don't care.
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
if (track == null || track.getTripStatistics() == null) {
startTime = -1L;
return;
}
startTime = track.getTripStatistics().getStartTime();
}
}
@Override
public void clearTrackPoints() {
if (isResumed()) {
tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null;
pendingPoints.clear();
chartView.reset();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.resetScroll();
}
}
});
}
}
@Override
public void onSampledInTrackPoint(Location location) {
if (isResumed()) {
double[] data = new double[ChartView.NUM_SERIES + 1];
fillDataPoint(location, data);
pendingPoints.add(data);
}
}
@Override
public void onSampledOutTrackPoint(Location location) {
if (isResumed()) {
fillDataPoint(location, null);
}
}
@Override
public void onSegmentSplit(Location location) {
if (isResumed()) {
fillDataPoint(location, null);
}
}
@Override
public void onNewTrackPointsDone() {
if (isResumed()) {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
getActivity().runOnUiThread(updateChart);
}
}
@Override
public void clearWaypoints() {
if (isResumed()) {
chartView.clearWaypoints();
}
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
chartView.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(updateChart);
}
}
@Override
public boolean onMetricUnitsChanged(boolean metric) {
if (isResumed()) {
if (metricUnits == metric) {
return false;
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.requestLayout();
}
}
});
return true;
}
return false;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (isResumed()) {
if (reportSpeed == speed) {
return false;
}
reportSpeed = speed;
chartView.setReportSpeed(reportSpeed);
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
chartView.requestLayout();
}
}
});
return true;
}
return false;
}
@Override
public boolean onMinRequiredAccuracy(int minRequiredAccuracy) {
// We don't care.
return false;
}
@Override
public boolean onMinRecordingDistanceChanged(int value) {
if (isResumed()) {
if (minRecordingDistance == value) {
return false;
}
minRecordingDistance = value;
return true;
}
return false;
}
/**
* Checks the chart settings.
*/
private void checkChartSettings() {
boolean needUpdate = false;
if (chartByDistance != PreferencesUtils.getBoolean(getActivity(),
R.string.chart_by_distance_key, PreferencesUtils.CHART_BY_DISTANCE_DEFAULT)) {
chartByDistance = !chartByDistance;
chartView.setChartByDistance(chartByDistance);
reloadTrackDataHub();
needUpdate = true;
}
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(),
R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
needUpdate = true;
}
boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(),
R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) {
needUpdate = true;
}
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(),
R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) {
needUpdate = true;
}
if (needUpdate) {
chartView.postInvalidate();
}
}
/**
* Sets the series enabled value.
*
* @param index the series index
* @param value the value
* @return true if changed
*/
private boolean setSeriesEnabled(int index, boolean value) {
if (chartShow[index] != value) {
chartShow[index] = value;
chartView.setChartValueSeriesEnabled(index, value);
return true;
} else {
return false;
}
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE,
TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, TrackDataType.PREFERENCE));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if the selected track is recording. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Reloads the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void reloadTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.reloadDataForListener(this);
}
}
/**
* To zoom in.
*/
private void zoomIn() {
chartView.zoomIn();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* To zoom out.
*/
private void zoomOut() {
chartView.zoomOut();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* Given a location, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
* data[3] = pace <br>
* data[4] = heart rate <br>
* data[5] = cadence <br>
* data[6] = power <br>
*
* @param location the location
* @param data the data point to fill in, can be null
*/
@VisibleForTesting
void fillDataPoint(Location location, double data[]) {
double timeOrDistance = Double.NaN;
double elevation = Double.NaN;
double speed = Double.NaN;
double pace = Double.NaN;
double heartRate = Double.NaN;
double cadence = Double.NaN;
double power = Double.NaN;
if (tripStatisticsUpdater != null) {
tripStatisticsUpdater.addLocation(location, minRecordingDistance);
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = tripStatistics.getTotalTime();
}
elevation = tripStatisticsUpdater.getSmoothedElevation();
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
}
if (location instanceof MyTracksLocation
&& ((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
if (sensorDataSet.hasHeartRate()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getHeartRate().hasValue()) {
heartRate = sensorDataSet.getHeartRate().getValue();
}
if (sensorDataSet.hasCadence()
&& sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getCadence().hasValue()) {
cadence = sensorDataSet.getCadence().getValue();
}
if (sensorDataSet.hasPower()
&& sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getPower().hasValue()) {
power = sensorDataSet.getPower().getValue();
}
}
if (data != null) {
data[0] = timeOrDistance;
data[1] = elevation;
data[2] = speed;
data[3] = pace;
data[4] = heartRate;
data[5] = cadence;
data[6] = power;
}
}
@VisibleForTesting
ChartView getChartView() {
return chartView;
}
@VisibleForTesting
void setTripStatisticsUpdater(long time) {
tripStatisticsUpdater = new TripStatisticsUpdater(time);
}
@VisibleForTesting
void setChartView(ChartView view) {
chartView = view;
}
@VisibleForTesting
void setMetricUnits(boolean value) {
metricUnits = value;
}
@VisibleForTesting
void setReportSpeed(boolean value) {
reportSpeed = value;
}
@VisibleForTesting
void setChartByDistance(boolean value) {
chartByDistance = value;
}
}
@@ -0,0 +1,126 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import java.io.IOException;
/**
* A non UI fragment to check permission.
*
* @author Jimmy Shih
*/
public class CheckPermissionFragment extends Fragment {
/**
* Interface for caller of this fragment.
*
* @author Jimmy Shih
*/
public interface CheckPermissionCaller {
/**
* Called when check permission is done.
*
* @param scope the permission scope
* @param success true if success
* @param intent if not success, intent to prompt for permission
*/
public void onCheckPermissionDone(String scope, boolean success, Intent intent);
}
public static final String CHECK_PERMISSION_TAG = "checkPermission";
private static final String TAG = CheckPermissionFragment.class.getSimpleName();
private static final String KEY_ACCOUNT_NAME = "accountName";
private static final String KEY_SCOPE = "scope";
private CheckPermissionCaller caller;
public static CheckPermissionFragment newInstance(String accountName, String scope) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, accountName);
bundle.putString(KEY_SCOPE, scope);
CheckPermissionFragment checkPermissionFragment = new CheckPermissionFragment();
checkPermissionFragment.setArguments(bundle);
return checkPermissionFragment;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (CheckPermissionCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + CheckPermissionCaller.class.getSimpleName());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Only want one instance
setRetainInstance(true);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String accountName = getArguments().getString(KEY_ACCOUNT_NAME);
String scope = getArguments().getString(KEY_SCOPE);
try {
SendToGoogleUtils.getGoogleAccountCredential(getActivity(), accountName, scope);
finish(scope, true, null);
} catch (UserRecoverableAuthException e) {
finish(scope, false, e.getIntent());
} catch (GoogleAuthException e) {
Log.e(TAG, "GoogleAuthException", e);
finish(scope, false, null);
} catch (UserRecoverableAuthIOException e) {
finish(scope, false, e.getIntent());
} catch (IOException e) {
Log.e(TAG, "IOException", e);
finish(scope, false, null);
}
}
});
thread.start();
}
private void finish(final String scope, final boolean success, final Intent intent) {
getFragmentManager().popBackStack();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
caller.onCheckPermissionDone(scope, success, intent);
}
});
}
}
@@ -0,0 +1,119 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* A DialogFragment to choose an account.
*
* @author Jimmy Shih
*/
public class ChooseAccountDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ChooseAccountCaller {
/**
* Called when choose account is done.
*/
public void onChooseAccountDone();
}
public static final String CHOOSE_ACCOUNT_DIALOG_TAG = "chooseAccount";
private ChooseAccountCaller caller;
private FragmentActivity fragmentActivity;
private Account[] accounts;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (ChooseAccountCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + ChooseAccountCaller.class.getSimpleName());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = getActivity();
accounts = AccountManager.get(fragmentActivity).getAccountsByType(Constants.ACCOUNT_TYPE);
if (accounts.length == 1) {
PreferencesUtils.setString(fragmentActivity, R.string.google_account_key, accounts[0].name);
dismiss();
caller.onChooseAccountDone();
return;
}
String googleAccount = PreferencesUtils.getString(
fragmentActivity, R.string.google_account_key, PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT);
for (int i = 0; i < accounts.length; i++) {
if (accounts[i].name.equals(googleAccount)) {
dismiss();
caller.onChooseAccountDone();
return;
}
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (accounts.length == 0) {
return new AlertDialog.Builder(fragmentActivity).setMessage(
R.string.send_google_no_account_message).setTitle(R.string.send_google_no_account_title)
.setPositiveButton(R.string.generic_ok, null).create();
}
String[] choices = new String[accounts.length];
for (int i = 0; i < accounts.length; i++) {
choices[i] = accounts[i].name;
}
return new AlertDialog.Builder(fragmentActivity).setNegativeButton(
R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
PreferencesUtils.setString(
fragmentActivity, R.string.google_account_key, accounts[position].name);
caller.onChooseAccountDone();
}
}).setSingleChoiceItems(choices, 0, null)
.setTitle(R.string.send_google_choose_account_title).create();
}
}
@@ -0,0 +1,260 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.ShareCompat;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
/**
* A DialogFragment to choose an activity.
*
* @author Jimmy Shih
*/
public class ChooseActivityDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ChooseActivityCaller {
/**
* Called when choose activity is done.
*/
public void onChooseActivityDone(String packageName, String className);
}
public static final String CHOOSE_ACTIVITY_DIALOG_TAG = "chooseActivityDialog";
private ChooseActivityCaller caller;
private FragmentActivity fragmentActivity;
private PackageManager packageManager;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (ChooseActivityCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + ChooseActivityCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
packageManager = fragmentActivity.getPackageManager();
List<DisplayInfo> displayInfos = getDisplayInfos();
ArrayAdapter<DisplayInfo> arrayAdapter = new ArrayAdapter<DisplayInfo>(fragmentActivity,
R.layout.choose_activity_list_item, R.id.choose_activity_list_item_text1, displayInfos) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = fragmentActivity.getLayoutInflater()
.inflate(R.layout.choose_activity_list_item, parent, false);
} else {
view = convertView;
}
DisplayInfo displayInfo = getItem(position);
TextView text1 = (TextView) view.findViewById(R.id.choose_activity_list_item_text1);
TextView text2 = (TextView) view.findViewById(R.id.choose_activity_list_item_text2);
ImageView icon = (ImageView) view.findViewById(R.id.choose_activity_list_item_icon);
text1.setText(displayInfo.primaryLabel);
if (displayInfo.secondaryLabel != null) {
text2.setVisibility(View.VISIBLE);
text2.setText(displayInfo.secondaryLabel);
} else {
text2.setVisibility(View.GONE);
}
icon.setImageDrawable(displayInfo.icon);
return view;
}
};
return new AlertDialog.Builder(fragmentActivity).setSingleChoiceItems(
arrayAdapter, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AlertDialog alertDialog = (AlertDialog) dialog;
DisplayInfo displayInfo = (DisplayInfo) alertDialog.getListView()
.getItemAtPosition(which);
ActivityInfo activityInfo = displayInfo.resolveInfo.activityInfo;
dismiss();
caller.onChooseActivityDone(
activityInfo.applicationInfo.packageName, activityInfo.name);
}
}).setTitle(R.string.share_track_picker_title).create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
caller.onChooseActivityDone(null, null);
}
/**
* Gets the display info.
*/
private List<DisplayInfo> getDisplayInfos() {
List<DisplayInfo> displayInfos = new ArrayList<DisplayInfo>();
Intent intent = ShareCompat.IntentBuilder.from(fragmentActivity)
.setType(IntentUtils.TEXT_PLAIN_TYPE).getIntent();
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfos != null && resolveInfos.size() > 0) {
int size = resolveInfos.size();
ResolveInfo firstResolveInfo = resolveInfos.get(0);
for (int i = 1; i < size; i++) {
ResolveInfo resolveInfo = resolveInfos.get(i);
if (firstResolveInfo.priority != resolveInfo.priority
|| firstResolveInfo.isDefault != resolveInfo.isDefault) {
while (i < size) {
resolveInfos.remove(i);
size--;
}
}
}
if (size > 1) {
ResolveInfo.DisplayNameComparator displayNameComparator = new ResolveInfo.DisplayNameComparator(
packageManager);
Collections.sort(resolveInfos, displayNameComparator);
}
firstResolveInfo = resolveInfos.get(0);
int start = 0;
CharSequence firstLabel = firstResolveInfo.loadLabel(packageManager);
for (int i = 1; i < size; i++) {
if (firstLabel == null) {
firstLabel = firstResolveInfo.activityInfo.packageName;
}
ResolveInfo resolveInfo = resolveInfos.get(i);
CharSequence label = resolveInfo.loadLabel(packageManager);
if (label == null) {
label = resolveInfo.activityInfo.packageName;
}
if (label.equals(firstLabel)) {
continue;
}
processGroup(resolveInfos, displayInfos, start, i - 1);
firstResolveInfo = resolveInfo;
firstLabel = label;
start = i;
}
// Process last group
processGroup(resolveInfos, displayInfos, start, size - 1);
}
return displayInfos;
}
/**
* Contains display info.
*
* @author Jimmy Shih
*/
private final class DisplayInfo {
private ResolveInfo resolveInfo;
private CharSequence primaryLabel;
private CharSequence secondaryLabel;
private Drawable icon;
public DisplayInfo(ResolveInfo resolveInfo, CharSequence primaryLabel,
CharSequence secondaryLabel, Drawable icon) {
this.resolveInfo = resolveInfo;
this.primaryLabel = primaryLabel;
this.secondaryLabel = secondaryLabel;
this.icon = icon;
}
}
/**
* Processes a group of items with the same label.
*
* @param resolveInfos list of resolve infos
* @param displayInfos list of display infos
* @param start start index
* @param end end index
*/
private void processGroup(
List<ResolveInfo> resolveInfos, List<DisplayInfo> displayInfos, int start, int end) {
ResolveInfo startResolveInfo = resolveInfos.get(start);
CharSequence primaryLabel = startResolveInfo.loadLabel(packageManager);
Drawable icon = startResolveInfo.loadIcon(packageManager);
int num = end - start + 1;
if (num == 1) {
// Only one, set the secondary label to null
displayInfos.add(new DisplayInfo(startResolveInfo, primaryLabel, null, icon));
} else {
// Decide package name or application name for the secondary label
boolean usePackageName = false;
CharSequence appName = startResolveInfo.activityInfo.applicationInfo.loadLabel(
packageManager);
if (appName == null) {
usePackageName = true;
} else {
// Use HashSet to track duplicates
HashSet<CharSequence> duplicates = new HashSet<CharSequence>();
duplicates.add(appName);
for (int i = start + 1; i <= end; i++) {
ResolveInfo resolveInfo = resolveInfos.get(i);
CharSequence name = resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager);
if ((name == null) || (duplicates.contains(name))) {
usePackageName = true;
break;
} else {
duplicates.add(name);
}
}
// Clear HashSet for later use
duplicates.clear();
}
for (int i = start; i <= end; i++) {
ResolveInfo resolveInfo = resolveInfos.get(i);
CharSequence secondaryLabel = usePackageName ? resolveInfo.activityInfo.packageName
: resolveInfo.activityInfo.applicationInfo.loadLabel(packageManager);
displayInfos.add(new DisplayInfo(resolveInfo, primaryLabel, secondaryLabel, icon));
}
}
}
}
@@ -0,0 +1,105 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import java.util.ArrayList;
import java.util.List;
/**
* A DialogFragment to choose an activity type.
*
* @author apoorvn
*/
public class ChooseActivityTypeDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author apoorvn
*/
public interface ChooseActivityTypeCaller {
/**
* Called when choose activity type is done.
*/
public void onChooseActivityTypeDone(String iconValue);
}
public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType";
private ChooseActivityTypeCaller caller;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (ChooseActivityTypeCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement "
+ ChooseActivityTypeCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
GridView gridView =
(GridView) getActivity().getLayoutInflater().inflate(R.layout.choose_activity_type, null);
final List<String> iconValues = TrackIconUtils.getAllIconValues();
List<Integer> imageIds = new ArrayList<Integer>();
for (String iconValue : iconValues) {
imageIds.add(TrackIconUtils.getIconDrawable(iconValue));
}
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.track_airplane, options);
int padding = 32;
int width = options.outWidth + 2 * padding;
int height = options.outHeight + 2 * padding;
gridView.setColumnWidth(width);
ChooseActivityTypeImageAdapter imageAdapter =
new ChooseActivityTypeImageAdapter(getActivity(), imageIds, width, height, padding);
gridView.setAdapter(imageAdapter);
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
dismiss();
caller.onChooseActivityTypeDone(iconValues.get(position));
}
});
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setTitle(R.string.track_edit_activity_type_hint).setView(gridView).create();
}
}
@@ -0,0 +1,78 @@
/*
* 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.fragments;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import java.util.List;
/**
* Image adapter for choosing an activity type.
*
* @author apoorvn
*/
public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final Context context;
private final List<Integer> imageIds;
private final int width;
private final int height;
private final int padding;
public ChooseActivityTypeImageAdapter(
Context context, List<Integer> imageIds, int width, int height, int padding) {
this.context = context;
this.imageIds = imageIds;
this.width = width;
this.height = height;
this.padding = padding;
}
@Override
public int getCount() {
return imageIds.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(context);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(imageIds.get(position));
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
}
}
@@ -0,0 +1,195 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.TableRow;
import android.widget.Toast;
/**
* A DialogFragment to choose upload service.
*
* @author Jimmy Shih
*/
public class ChooseUploadServiceDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ChooseUploadServiceCaller {
/**
* Called when choose upload service is done.
*/
public void onChooseUploadServiceDone(boolean sendDrive, boolean sendMaps,
boolean sendFusionTables, boolean sendSpreadsheets, boolean mapsExistingMap);
}
public static final String CHOOSE_UPLOAD_SERVICE_DIALOG_TAG = "chooseUploadService";
private static final String KEY_HIDE_DRIVE = "hideDrive";
public static ChooseUploadServiceDialogFragment newInstance(boolean hideDrive) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_HIDE_DRIVE, hideDrive);
ChooseUploadServiceDialogFragment chooseUploadServiceDialogFragment = new ChooseUploadServiceDialogFragment();
chooseUploadServiceDialogFragment.setArguments(bundle);
return chooseUploadServiceDialogFragment;
}
private ChooseUploadServiceCaller caller;
private FragmentActivity fragmentActivity;
private boolean hideDrive;
private CheckBox driveCheckBox;
private CheckBox mapsCheckBox;
private CheckBox fusionTablesCheckBox;
private CheckBox spreadsheetsCheckBox;
private TableRow mapsOptionTableRow;
private RadioButton existingMapRadioButton;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (ChooseUploadServiceCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement "
+ ChooseUploadServiceCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
hideDrive = getArguments().getBoolean(KEY_HIDE_DRIVE);
View view = fragmentActivity.getLayoutInflater().inflate(R.layout.choose_upload_service, null);
view.findViewById(R.id.choose_upload_service_drive_options)
.setVisibility(hideDrive ? View.GONE : View.VISIBLE);
driveCheckBox = (CheckBox) view.findViewById(R.id.choose_upload_service_drive);
mapsCheckBox = (CheckBox) view.findViewById(R.id.choose_upload_service_maps);
fusionTablesCheckBox = (CheckBox) view.findViewById(R.id.choose_upload_service_fusion_tables);
spreadsheetsCheckBox = (CheckBox) view.findViewById(R.id.choose_upload_service_spreadsheets);
driveCheckBox.setChecked(PreferencesUtils.getBoolean(
fragmentActivity, R.string.send_to_drive_key, PreferencesUtils.SEND_TO_DRIVE_DEFAULT));
mapsCheckBox.setChecked(PreferencesUtils.getBoolean(
fragmentActivity, R.string.send_to_maps_key, PreferencesUtils.SEND_TO_MAPS_DEFAULT));
fusionTablesCheckBox.setChecked(PreferencesUtils.getBoolean(
fragmentActivity, R.string.send_to_fusion_tables_key,
PreferencesUtils.SEND_TO_FUSION_TABLES_DEFAULT));
spreadsheetsCheckBox.setChecked(PreferencesUtils.getBoolean(
fragmentActivity, R.string.send_to_spreadsheets_key,
PreferencesUtils.SEND_TO_SPREADSHEETS_DEFAULT));
mapsCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean checked) {
updateMapsOption();
}
});
mapsOptionTableRow = (TableRow) view.findViewById(R.id.choose_upload_service_maps_options);
RadioButton newMapRadioButton = (RadioButton) view.findViewById(
R.id.choose_upload_service_new_map);
boolean defaultMapPublic = PreferencesUtils.getBoolean(
fragmentActivity, R.string.default_map_public_key, PreferencesUtils.DEFAULT_MAP_PUBLIC_DEFAULT);
newMapRadioButton.setText(defaultMapPublic ? R.string.send_google_new_public_map
: R.string.send_google_new_unlisted_map);
existingMapRadioButton = (RadioButton) view.findViewById(
R.id.choose_upload_service_existing_map);
updateMapsOption();
if (PreferencesUtils.getBoolean(
fragmentActivity, R.string.pick_existing_map_key, PreferencesUtils.PICK_EXISTING_MAP_DEFAULT)) {
existingMapRadioButton.setChecked(true);
} else {
newMapRadioButton.setChecked(true);
}
// Update fusion table
boolean defaultTablePublic = PreferencesUtils.getBoolean(
fragmentActivity, R.string.default_table_public_key, PreferencesUtils.DEFAULT_TABLE_PUBLIC_DEFAULT);
String text = getString(R.string.send_google_fusion_tables) + "\n"
+ getString(defaultTablePublic ? R.string.send_google_fusion_tables_public
: R.string.send_google_fusion_tables_private);
fusionTablesCheckBox.setText(text);
return new AlertDialog.Builder(fragmentActivity).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.send_google_send_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PreferencesUtils.setBoolean(
fragmentActivity, R.string.send_to_drive_key, driveCheckBox.isChecked());
PreferencesUtils.setBoolean(
fragmentActivity, R.string.pick_existing_map_key, existingMapRadioButton.isChecked());
PreferencesUtils.setBoolean(
fragmentActivity, R.string.send_to_maps_key, mapsCheckBox.isChecked());
PreferencesUtils.setBoolean(
fragmentActivity, R.string.send_to_fusion_tables_key, fusionTablesCheckBox.isChecked());
PreferencesUtils.setBoolean(
fragmentActivity, R.string.send_to_spreadsheets_key, spreadsheetsCheckBox.isChecked());
if (sendDrive() || mapsCheckBox.isChecked() || fusionTablesCheckBox.isChecked()
|| spreadsheetsCheckBox.isChecked()) {
caller.onChooseUploadServiceDone(sendDrive(), mapsCheckBox.isChecked(),
fusionTablesCheckBox.isChecked(), spreadsheetsCheckBox.isChecked(),
existingMapRadioButton.isChecked());
} else {
Toast.makeText(fragmentActivity, R.string.send_google_no_service_selected, Toast.LENGTH_LONG)
.show();
}
}
}).setTitle(R.string.send_google_title).setView(view).create();
}
/**
* Updates map option.
*/
private void updateMapsOption() {
mapsOptionTableRow.setVisibility(mapsCheckBox.isChecked() ? View.VISIBLE : View.GONE);
}
/**
* True to send to Google Drive.
*/
private boolean sendDrive() {
return !hideDrive && driveCheckBox.isChecked();
}
}
@@ -0,0 +1,120 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
/**
* A DialogFragment to confirm an action.
*
* @author Jimmy Shih
*/
public class ConfirmDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ConfirmCaller {
/**
* Called when confirm is done.
*/
public void onConfirmDone(int confirmId, long[] trackIds);
}
public static final String CONFIRM_DIALOG_TAG = "confirmDialog";
private static final String KEY_CONFIRM_ID = "confirmId";
private static final String KEY_DEFAULT_VALUE = "defaultValue";
private static final String KEY_MESSAGE = "message";
private static final String KEY_TRACK_IDS = "trackIds";
private CheckBox checkBox;
public static ConfirmDialogFragment newInstance(
int confirmId, boolean defaultValue, CharSequence message, long[] trackIds) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_CONFIRM_ID, confirmId);
bundle.putBoolean(KEY_DEFAULT_VALUE, defaultValue);
bundle.putCharSequence(KEY_MESSAGE, message);
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
ConfirmDialogFragment confirmDialogFragment = new ConfirmDialogFragment();
confirmDialogFragment.setArguments(bundle);
return confirmDialogFragment;
}
private ConfirmCaller caller;
private FragmentActivity fragmentActivity;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (ConfirmCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + ConfirmCaller.class.getSimpleName());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int confirmId = getArguments().getInt(KEY_CONFIRM_ID);
boolean defaultValue = getArguments().getBoolean(KEY_DEFAULT_VALUE);
fragmentActivity = getActivity();
if (!PreferencesUtils.getBoolean(fragmentActivity, confirmId, defaultValue)) {
long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
dismiss();
caller.onConfirmDone(confirmId, trackIds);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = fragmentActivity.getLayoutInflater().inflate(R.layout.confirm_dialog, null);
TextView textView = (TextView) view.findViewById(R.id.confirm_dialog_message);
textView.setText(getArguments().getCharSequence(KEY_MESSAGE));
checkBox = (CheckBox) view.findViewById(R.id.confirm_dialog_check_box);
return new AlertDialog.Builder(fragmentActivity).setNegativeButton(R.string.generic_no, null)
.setPositiveButton(R.string.generic_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int confirmId = getArguments().getInt(KEY_CONFIRM_ID);
long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
PreferencesUtils.setBoolean(fragmentActivity, confirmId, !checkBox.isChecked());
caller.onConfirmDone(confirmId, trackIds);
}
}).setTitle(R.string.generic_confirm_title).setView(view).create();
}
}
@@ -0,0 +1,99 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* A DialogFragment to delete marker.
*
* @author Jimmy Shih
*/
public class DeleteMarkerDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface DeleteMarkerCaller {
/**
* Called when delete marker is done.
*/
public void onDeleteMarkerDone();
}
public static final String DELETE_MARKER_DIALOG_TAG = "deleteMarkerDialog";
private static final String KEY_MARKER_IDS = "markerIds";
public static DeleteMarkerDialogFragment newInstance(long[] markerIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_MARKER_IDS, markerIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle);
return deleteMarkerDialogFragment;
}
private DeleteMarkerCaller caller;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (DeleteMarkerCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + DeleteMarkerCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity fragmentActivity = getActivity();
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
int messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message
: R.string.marker_delete_one_confirm_message;
return DialogUtils.createConfirmationDialog(
fragmentActivity, messageId, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new Thread(new Runnable() {
@Override
public void run() {
for (long markerId : markerIds) {
MyTracksProviderUtils.Factory.get(fragmentActivity)
.deleteWaypoint(markerId, new DescriptionGeneratorImpl(fragmentActivity));
}
caller.onDeleteMarkerDone();
}
}).start();
}
});
}
}
@@ -0,0 +1,142 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* A DialogFragment to track.
*
* @author Jimmy Shih
*/
public class DeleteTrackDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface DeleteTrackCaller {
/**
* Gets the track recording service connection.
*/
public TrackRecordingServiceConnection getTrackRecordingServiceConnection();
/**
* Called when delete track is done.
*/
public void onDeleteTrackDone();
}
public static final String DELETE_TRACK_DIALOG_TAG = "deleteTrackDialog";
private static final String KEY_DELETE_ALL = "deleteAll";
private static final String KEY_TRACK_IDS = "trackIds";
/**
* Create a new instance.
*
* @param deleteAll true to delete all tracks
* @param trackIds list of track ids to delete when deleteAll if false
*/
public static DeleteTrackDialogFragment newInstance(boolean deleteAll, long[] trackIds) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_DELETE_ALL, deleteAll);
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
DeleteTrackDialogFragment deleteTrackDialogFragment = new DeleteTrackDialogFragment();
deleteTrackDialogFragment.setArguments(bundle);
return deleteTrackDialogFragment;
}
private DeleteTrackCaller caller;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (DeleteTrackCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + DeleteTrackCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
boolean deleteAll = getArguments().getBoolean(KEY_DELETE_ALL);
final FragmentActivity fragmentActivity = getActivity();
if (deleteAll) {
return DialogUtils.createConfirmationDialog(fragmentActivity,
R.string.track_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new Thread(new Runnable() {
@Override
public void run() {
PreferencesUtils.setBoolean(fragmentActivity, R.string.drive_sync_key,
PreferencesUtils.DRIVE_SYNC_DEFAULT);
SyncUtils.disableSync(fragmentActivity);
SyncUtils.clearSyncState(fragmentActivity);
MyTracksProviderUtils.Factory.get(fragmentActivity).deleteAllTracks();
caller.onDeleteTrackDone();
}
}).start();
}
});
} else {
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message
: R.string.track_delete_one_confirm_message;
return DialogUtils.createConfirmationDialog(
fragmentActivity, messageId, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
for (long trackId : trackIds) {
if (trackId == PreferencesUtils.getLong(
fragmentActivity, R.string.recording_track_id_key)) {
TrackRecordingServiceConnectionUtils.stopRecording(
fragmentActivity, caller.getTrackRecordingServiceConnection(), false);
}
}
new Thread(new Runnable() {
@Override
public void run() {
for (long id : trackIds) {
MyTracksProviderUtils.Factory.get(fragmentActivity).deleteTrack(id);
}
caller.onDeleteTrackDone();
}
}).start();
}
});
}
}
}
@@ -0,0 +1,106 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* A DialogFrament to enable sync.
*
* @author Jimmy Shih
*/
public class EnableSyncDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface EnableSyncCaller {
/**
* Called when enable sync is done.
*/
public void onEnableSyncDone(boolean enable);
}
public static final String ENABLE_SYNC_DIALOG_TAG = "enableSyncDialog";
private EnableSyncCaller caller;
private FragmentActivity fragmentActivity;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (EnableSyncCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + EnableSyncCaller.class.getSimpleName());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = getActivity();
if (PreferencesUtils.getBoolean(
fragmentActivity, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT)) {
dismiss();
caller.onEnableSyncDone(false);
return;
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(fragmentActivity).setMessage(R.string.enable_sync_message)
.setNegativeButton(R.string.generic_no, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
caller.onEnableSyncDone(false);
}
}).setPositiveButton(R.string.generic_yes, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
/*
* google_account_key can be set from the android backup service. If
* the user wants to enable sync, make sure google_account_key is
* cleared so the user can choose an account.
*/
PreferencesUtils.setString(fragmentActivity, R.string.google_account_key,
PreferencesUtils.GOOGLE_ACCOUNT_DEFAULT);
caller.onEnableSyncDone(true);
}
}).setTitle(R.string.enable_sync_title).create();
}
@Override
public void onCancel(DialogInterface arg0) {
caller.onEnableSyncDone(false);
}
}
@@ -0,0 +1,142 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.EulaUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import java.util.Locale;
/**
* A DialogFragment to show EULA.
*
* @author Jimmy Shih
*/
public class EulaDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface EulaCaller {
/**
* Called when eula is done.
*/
public void onEulaDone();
}
public static final String EULA_DIALOG_TAG = "eulaDialog";
private static final String KEY_HAS_ACCEPTED = "hasAccepted";
private static final String GOOGLE_URL = "m.google.com";
private static final String KOREAN = "ko";
/**
* Creates a new instance of {@link EulaDialogFragment}.
*
* @param hasAccepted true if the user has accepted the eula.
*/
public static EulaDialogFragment newInstance(boolean hasAccepted) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_HAS_ACCEPTED, hasAccepted);
EulaDialogFragment eulaDialogFragment = new EulaDialogFragment();
eulaDialogFragment.setArguments(bundle);
return eulaDialogFragment;
}
private EulaCaller caller;
private FragmentActivity fragmentActivity;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (EulaCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + EulaCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
boolean hasAccepted = getArguments().getBoolean(KEY_HAS_ACCEPTED);
AlertDialog.Builder builder = new AlertDialog.Builder(fragmentActivity).setMessage(
getEulaText()).setTitle(R.string.eula_title);
if (hasAccepted) {
builder.setPositiveButton(R.string.generic_ok, null);
} else {
builder.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
caller.onEulaDone();
}
}).setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
return true;
}
return false;
}
}).setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaUtils.setAcceptEula(fragmentActivity);
PreferencesUtils.setBoolean(
fragmentActivity, R.string.metric_units_key, !Locale.US.equals(Locale.getDefault()));
caller.onEulaDone();
}
});
}
return builder.create();
}
@Override
public void onCancel(DialogInterface arg0) {
caller.onEulaDone();
}
/**
* Gets the EULA text.
*/
private String getEulaText() {
String tos = getString(R.string.eula_date) + "\n\n" + getString(R.string.eula_body, GOOGLE_URL)
+ "\n\n" + getString(R.string.eula_footer, GOOGLE_URL) + "\n\n"
+ getString(R.string.eula_copyright_year);
boolean isKorean = getResources().getConfiguration().locale.getLanguage().equals(KOREAN);
if (isKorean) {
tos += "\n\n" + getString(R.string.eula_korean);
}
return tos;
}
}
@@ -0,0 +1,103 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
/**
* A DialogFragment to select a file type, gpx, kml, etc.
*
* @author Jimmy Shih
*/
public class FileTypeDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface FileTypeCaller {
/**
* Called when file type selection is done.
*/
public void onFileTypeDone(int menuId, TrackFileFormat trackFileFormat);
}
public static final String FILE_TYPE_DIALOG_TAG = "fileType";
private static final String KEY_MENU_ID = "menuId";
private static final String KEY_TITLE_ID = "titleId";
private static final String KEY_OPTION_ID = "optionId";
private static final String KEY_SIZE = "size";
public static FileTypeDialogFragment newInstance(
int menuId, int titleId, int optionId, int size) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_MENU_ID, menuId);
bundle.putInt(KEY_TITLE_ID, titleId);
bundle.putInt(KEY_OPTION_ID, optionId);
bundle.putInt(KEY_SIZE, size);
FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment();
fileTypeDialogFragment.setArguments(bundle);
return fileTypeDialogFragment;
}
private FileTypeCaller caller;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
caller = (FileTypeCaller) activity;
} catch (ClassCastException e) {
throw new ClassCastException(
activity.toString() + " must implement " + FileTypeCaller.class.getSimpleName());
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String fileTypes[] = getResources().getStringArray(R.array.file_types);
int size = getArguments().getInt(KEY_SIZE);
int optionId = getArguments().getInt(KEY_OPTION_ID);
final int titleId = getArguments().getInt(KEY_TITLE_ID);
final int menuId = getArguments().getInt(KEY_MENU_ID);
String[] choices = new String[size];
for (int i = 0; i < choices.length; i++) {
choices[i] = getString(optionId, fileTypes[i]);
}
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
caller.onFileTypeDone(menuId, TrackFileFormat.values()[position]);
}
}).setSingleChoiceItems(choices, 0, null).setTitle(titleId).create();
}
}
@@ -0,0 +1,111 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
/**
* A DialogFragment to configure frequency.
*
* @author Jimmy Shih
*/
public class FrequencyDialogFragment extends DialogFragment {
public static final String FREQUENCY_DIALOG_TAG = "frequencyDialog";
private static final String KEY_PREFERENCE_ID = "preferenceId";
private static final String KEY_DEFAULT_VALUE = "defaultValue";
private static final String KEY_TITLE_ID = "titleId";
public static FrequencyDialogFragment newInstance(
int preferenceId, int defaultValue, int titleId) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_PREFERENCE_ID, preferenceId);
bundle.putInt(KEY_DEFAULT_VALUE, defaultValue);
bundle.putInt(KEY_TITLE_ID, titleId);
FrequencyDialogFragment frequencyDialogFragment = new FrequencyDialogFragment();
frequencyDialogFragment.setArguments(bundle);
return frequencyDialogFragment;
}
private FragmentActivity fragmentActivity;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
final int preferenceId = getArguments().getInt(KEY_PREFERENCE_ID);
int defaultValue = getArguments().getInt(KEY_DEFAULT_VALUE);
int titleId = getArguments().getInt(KEY_TITLE_ID);
int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue);
return new AlertDialog.Builder(fragmentActivity)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
PreferencesUtils.setInt(fragmentActivity, preferenceId, getFrequencyValue(listIndex));
}
})
.setSingleChoiceItems(getFrequencyDisplayOptions(), getListIndex(frequencyValue), null)
.setTitle(titleId)
.create();
}
/**
* Gets the frequency display options.
*/
private String[] getFrequencyDisplayOptions() {
boolean metricUnits = PreferencesUtils.getBoolean(
fragmentActivity, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits);
}
/**
* Gets the list index for a frequency value. Returns 0 if the value is not on
* the list.
*/
private int getListIndex(int frequencyValue) {
String[] values = getResources().getStringArray(R.array.frequency_values);
for (int i = 0; i < values.length; i++) {
if (frequencyValue == Integer.parseInt(values[i])) {
return i;
}
}
return 0;
}
/**
* Gets the frequency value from a list index.
*
* @param listIndex the list index
*/
private int getFrequencyValue(int listIndex) {
String[] values = getResources().getStringArray(R.array.frequency_values);
return Integer.parseInt(values[listIndex]);
}
}
@@ -0,0 +1,64 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.util.GoogleEarthUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
/**
* A DialogFragment to install Google Earth.
*
* @author Jimmy Shih
*/
public class InstallEarthDialogFragment extends DialogFragment {
public static final String INSTALL_EARTH_DIALOG_TAG = "installEarthDialog";
private FragmentActivity fragmentActivity;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
fragmentActivity = getActivity();
return new AlertDialog.Builder(fragmentActivity).setMessage(
R.string.track_detail_install_earth_message).setNegativeButton(R.string.generic_no, null)
.setPositiveButton(R.string.generic_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent().setData(
Uri.parse(GoogleEarthUtils.GOOGLE_EARTH_MARKET_URL));
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(
fragmentActivity, R.string.track_detail_install_earth_error, Toast.LENGTH_LONG)
.show();
}
}
}).setTitle(R.string.generic_confirm_title).create();
}
}
@@ -0,0 +1,753 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.MapOverlay;
import com.google.android.apps.mytracks.MarkerDetailActivity;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.MyTracksLocationManager;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.GoogleLocationUtils;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.LocationSource;
import com.google.android.gms.maps.LocationSource.OnLocationChangedListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* A fragment to display map to the user.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class MyTracksMapFragment extends SupportMapFragment implements TrackDataListener {
public static final String MAP_FRAGMENT_TAG = "mapFragment";
private static final String CURRENT_LOCATION_KEY = "current_location_key";
private static final String
KEEP_CURRENT_LOCATION_VISIBLE_KEY = "keep_current_location_visible_key";
private static final String ZOOM_TO_CURRENT_LOCATION_KEY = "zoom_to_current_location_key";
private static final float DEFAULT_ZOOM_LEVEL = 18f;
// Google's latitude and longitude
private static final double DEFAULT_LATITUDE = 37.423;
private static final double DEFAULT_LONGITUDE = -122.084;
private static final int MAP_VIEW_PADDING = 32;
private TrackDataHub trackDataHub;
// Current location
private Location currentLocation;
/**
* True to continue keeping the current location visible on the screen.
* <p>
* Set to true when <br>
* 1. user clicks on the my location button <br>
* Set to false when <br>
* 1. showing a marker <br>
* 2. user manually zooms/pans
*/
private boolean keepCurrentLocationVisible;
/**
* True to zoom/center the current location when it is available.
* <p>
* Set to true when <br>
* 1. user clicks on the my location button <br>
* 2. first location during a recording <br>
* Set to false when <br>
* 1. showing a marker <br>
* 2. user manually zooms/pans <br>
* 3. after zooming to the current location <br>
* <p>
* The last one is to support the use case of zooming only once. After zoom,
* set it to false. E.g., only zoom to the first location during a recording.
*/
private boolean zoomToCurrentLocation;
private OnLocationChangedListener onLocationChangedListener;
// For showing a marker
private long markerTrackId = -1L;
private long markerId = -1L;
// Current track
private Track currentTrack;
// Current paths
private ArrayList<Polyline> paths = new ArrayList<Polyline>();
boolean reloadPaths = true;
// UI elements
private GoogleMap googleMap;
private MapOverlay mapOverlay;
private View mapView;
private ImageButton myLocationImageButton;
private TextView messageTextView;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setHasOptionsMenu(true);
mapOverlay = new MapOverlay(getActivity());
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mapView = super.onCreateView(inflater, container, savedInstanceState);
View layout = inflater.inflate(R.layout.map, container, false);
RelativeLayout mapContainer = (RelativeLayout) layout.findViewById(R.id.map_container);
mapContainer.addView(mapView, 0);
myLocationImageButton = (ImageButton) layout.findViewById(R.id.map_my_location);
myLocationImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final MyTracksLocationManager myTracksLocationManager = new MyTracksLocationManager(
getActivity(), Looper.myLooper());
if (!myTracksLocationManager.isAllowed()) {
String setting = getString(
GoogleLocationUtils.isAvailable(getActivity()) ? R.string.gps_google_location_settings
: R.string.gps_location_access);
Toast.makeText(
getActivity(), getString(R.string.my_location_no_gps, setting), Toast.LENGTH_LONG)
.show();
myTracksLocationManager.close();
} else {
myTracksLocationManager.requestLastLocation(new LocationListener() {
@Override
public void onLocationChanged(Location location) {
myTracksLocationManager.close();
keepCurrentLocationVisible = true;
zoomToCurrentLocation = true;
setCurrentLocation(location);
updateCurrentLocation();
}
});
}
}
});
messageTextView = (TextView) layout.findViewById(R.id.map_message);
/*
* At this point, after super.onCreateView, getMap will not return null and
* we can initialize googleMap. However, onCreateView can be called multiple
* times, e.g., when the user switches tabs. With
* GoogleMapOptions.useViewLifecycleInFragment == false, googleMap lifecycle
* is tied to the fragment lifecycle and the same googleMap object is
* returned in getMap. Thus we only need to initialize googleMap once, when
* it is null.
*/
if (googleMap == null) {
googleMap = getMap();
googleMap.setMyLocationEnabled(true);
/*
* My Tracks needs to handle the onClick event when the my location button
* is clicked. Currently, the API doesn't allow handling onClick event,
* thus hiding the default my location button and providing our own.
*/
googleMap.getUiSettings().setMyLocationButtonEnabled(false);
googleMap.setIndoorEnabled(true);
googleMap.setOnMarkerClickListener(new OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
if (isResumed()) {
String title = marker.getTitle();
if (title != null && title.length() > 0) {
long id = Long.valueOf(title);
Context context = getActivity();
Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
context.startActivity(intent);
}
}
return true;
}
});
googleMap.setLocationSource(new LocationSource() {
@Override
public void activate(OnLocationChangedListener listener) {
onLocationChangedListener = listener;
}
@Override
public void deactivate() {
onLocationChangedListener = null;
}
});
googleMap.setOnCameraChangeListener(new OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition cameraPosition) {
if (isResumed() && keepCurrentLocationVisible && currentLocation != null
&& !isLocationVisible(currentLocation)) {
keepCurrentLocationVisible = false;
zoomToCurrentLocation = false;
}
}
});
googleMap.moveCamera(
CameraUpdateFactory.newLatLngZoom(getDefaultLatLng(), googleMap.getMinZoomLevel()));
}
return layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
keepCurrentLocationVisible = savedInstanceState.getBoolean(
KEEP_CURRENT_LOCATION_VISIBLE_KEY, false);
zoomToCurrentLocation = savedInstanceState.getBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, false);
Location location = (Location) savedInstanceState.getParcelable(CURRENT_LOCATION_KEY);
setCurrentLocation(location);
}
}
@Override
public void onResume() {
super.onResume();
if (googleMap != null) {
int mapType = PreferencesUtils.getInt(
getActivity(), R.string.map_type_key, PreferencesUtils.MAP_TYPE_DEFAUlT);
googleMap.setMapType(mapType);
ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity());
}
resumeTrackDataHub();
updateCurrentLocation();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (currentLocation != null) {
outState.putParcelable(CURRENT_LOCATION_KEY, currentLocation);
}
outState.putBoolean(KEEP_CURRENT_LOCATION_VISIBLE_KEY, keepCurrentLocationVisible);
outState.putBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, zoomToCurrentLocation);
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
/**
* Shows the marker on the map.
*
* @param trackId the track id
* @param id the marker id
*/
public void showMarker(long trackId, long id) {
/*
* Synchronize to prevent race condition in changing markerTrackId and
* markerId variables.
*/
synchronized (this) {
if (currentTrack != null && currentTrack.getId() == trackId) {
showMarker(id);
markerTrackId = -1L;
markerId = -1L;
return;
}
markerTrackId = trackId;
markerId = id;
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) {
menuInflator.inflate(R.menu.map, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
if (googleMap != null) {
int id;
switch (googleMap.getMapType()) {
case GoogleMap.MAP_TYPE_NORMAL:
id = R.id.menu_map;
break;
case GoogleMap.MAP_TYPE_SATELLITE:
id = R.id.menu_satellite;
break;
case GoogleMap.MAP_TYPE_HYBRID:
id = R.id.menu_satellite_with_streets;
break;
case GoogleMap.MAP_TYPE_TERRAIN:
id = R.id.menu_terrain;
break;
default:
id = R.id.menu_map;
}
MenuItem menuItem = menu.findItem(id);
if (menuItem != null) {
menuItem.setChecked(true);
}
}
super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
int type = GoogleMap.MAP_TYPE_NORMAL;
switch (menuItem.getItemId()) {
case R.id.menu_map:
type = GoogleMap.MAP_TYPE_NORMAL;
break;
case R.id.menu_satellite:
type = GoogleMap.MAP_TYPE_SATELLITE;
break;
case R.id.menu_satellite_with_streets:
type = GoogleMap.MAP_TYPE_HYBRID;
break;
case R.id.menu_terrain:
type = GoogleMap.MAP_TYPE_TERRAIN;
break;
default:
return super.onOptionsItemSelected(menuItem);
}
if (googleMap != null) {
googleMap.setMapType(type);
menuItem.setChecked(true);
PreferencesUtils.setInt(getActivity(), R.string.map_type_key, type);
}
return true;
}
@Override
public void onLocationStateChanged(final LocationState state) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!isResumed() || googleMap == null) {
return;
}
boolean myLocationEnabled = true;
if (state == LocationState.DISABLED) {
setCurrentLocation(null);
myLocationEnabled = false;
}
googleMap.setMyLocationEnabled(myLocationEnabled);
String message;
boolean isGpsDisabled;
if (!isSelectedTrackRecording()) {
message = null;
isGpsDisabled = false;
} else {
switch (state) {
case DISABLED:
String setting = getString(GoogleLocationUtils.isAvailable(getActivity())
? R.string.gps_google_location_settings
: R.string.gps_location_access);
message = getString(R.string.gps_disabled, setting);
isGpsDisabled = true;
break;
case NO_FIX:
message = getString(R.string.gps_wait_for_signal);
isGpsDisabled = false;
break;
case BAD_FIX:
message = getString(R.string.gps_wait_for_better_signal);
isGpsDisabled = false;
break;
case GOOD_FIX:
message = null;
isGpsDisabled = false;
break;
default:
throw new IllegalArgumentException("Unexpected state: " + state);
}
}
if (message == null) {
messageTextView.setVisibility(View.GONE);
return;
}
messageTextView.setText(message);
messageTextView.setVisibility(View.VISIBLE);
if (isGpsDisabled) {
Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();
// Click to show the location source settings
messageTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = GoogleLocationUtils.isAvailable(getActivity()) ? new Intent(
GoogleLocationUtils.ACTION_GOOGLE_LOCATION_SETTINGS)
: new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
} else {
messageTextView.setOnClickListener(null);
}
}
});
}
}
@Override
public void onLocationChanged(final Location location) {
if (isResumed()) {
setCurrentLocation(location);
updateCurrentLocation();
}
}
@Override
public void onSelectedTrackChanged(final Track track) {
if (isResumed()) {
currentTrack = track;
boolean hasTrack = track != null;
if (hasTrack) {
mapOverlay.setShowEndMarker(!isSelectedTrackRecording());
synchronized (this) {
/*
* Synchronize to prevent race condition in changing markerTrackId and
* markerId variables.
*/
if (track.getId() == markerTrackId) {
// Show the marker
showMarker(markerId);
markerTrackId = -1L;
markerId = -1L;
} else {
// Show the track
showTrack();
}
}
}
}
}
@Override
public void onTrackUpdated(Track track) {
// We don't care.
}
@Override
public void clearTrackPoints() {
if (isResumed()) {
mapOverlay.clearPoints();
reloadPaths = true;
}
}
@Override
public void onSampledInTrackPoint(final Location location) {
if (isResumed()) {
mapOverlay.addLocation(location);
}
}
@Override
public void onSampledOutTrackPoint(Location location) {
// We don't care.
}
@Override
public void onSegmentSplit(Location location) {
if (isResumed()) {
mapOverlay.addSegmentSplit();
}
}
@Override
public void onNewTrackPointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
if (isResumed() && googleMap != null) {
mapOverlay.update(googleMap, paths, reloadPaths);
reloadPaths = false;
}
}
});
}
}
@Override
public void clearWaypoints() {
if (isResumed()) {
mapOverlay.clearWaypoints();
}
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
mapOverlay.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
if (isResumed() && googleMap != null) {
mapOverlay.update(googleMap, paths, true);
}
}
});
}
}
@Override
public boolean onMetricUnitsChanged(boolean metric) {
// We don't care.
return false;
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
// We don't care.
return false;
}
@Override
public boolean onMinRequiredAccuracy(int minRequiredAccuracy) {
// We don't care.
return false;
}
@Override
public boolean onMinRecordingDistanceChanged(int minRecordingDistance) {
// We don't care.
return false;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because the trackDataHub
* can be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK,
TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.LOCATION));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because the trackDataHub
* can be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.unregisterTrackDataListener(this);
}
trackDataHub = null;
}
/**
* Returns true if the selected track is recording. Needs to be synchronized
* because the trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Sets the current location.
*
* @param location the location
*/
private void setCurrentLocation(Location location) {
// If recording, zoom to the first location
if (isSelectedTrackRecording() && currentLocation == null && location != null) {
zoomToCurrentLocation = true;
}
currentLocation = location;
}
/**
* Updates the current location and zoom to it if necessary.
*/
private void updateCurrentLocation() {
getActivity().runOnUiThread(new Runnable() {
public void run() {
if (!isResumed() || googleMap == null || onLocationChangedListener == null
|| currentLocation == null) {
return;
}
onLocationChangedListener.onLocationChanged(currentLocation);
if (zoomToCurrentLocation
|| (keepCurrentLocationVisible && !isLocationVisible(currentLocation))) {
LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude());
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM_LEVEL));
zoomToCurrentLocation = false;
}
};
});
}
/**
* Sets the camera over a track.
*/
private void showTrack() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!isResumed() || googleMap == null || currentTrack == null
|| currentTrack.getNumberOfPoints() < 2) {
return;
}
/**
* Check that mapView is valid.
*/
if (mapView == null || mapView.getWidth() == 0 || mapView.getHeight() == 0) {
return;
}
TripStatistics tripStatistics = currentTrack.getTripStatistics();
int latitudeSpanE6 = tripStatistics.getTop() - tripStatistics.getBottom();
int longitudeSpanE6 = tripStatistics.getRight() - tripStatistics.getLeft();
if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0
&& longitudeSpanE6 < 360E6) {
LatLng southWest = new LatLng(
tripStatistics.getBottomDegrees(), tripStatistics.getLeftDegrees());
LatLng northEast = new LatLng(
tripStatistics.getTopDegrees(), tripStatistics.getRightDegrees());
LatLngBounds bounds = LatLngBounds.builder()
.include(southWest).include(northEast).build();
/**
* Note cannot call CameraUpdate.newLatLngBounds(LatLngBounds bounds,
* int padding) if the map view has not undergone layout. Thus calling
* CameraUpdate.newLatLngBounds(LatLngBounds bounds, int width, int
* height, int padding) after making sure that mapView is valid in the
* above code.
*/
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(
bounds, mapView.getWidth(), mapView.getHeight(), MAP_VIEW_PADDING);
googleMap.moveCamera(cameraUpdate);
}
}
});
}
/**
* Sets the camera over a marker.
*
* @param id the marker id
*/
private void showMarker(final long id) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!isResumed() || googleMap == null) {
return;
}
MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
if (waypoint != null && waypoint.getLocation() != null) {
Location location = waypoint.getLocation();
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
keepCurrentLocationVisible = false;
zoomToCurrentLocation = false;
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, DEFAULT_ZOOM_LEVEL);
googleMap.moveCamera(cameraUpdate);
}
}
});
}
/**
* Gets the default LatLng.
*/
private LatLng getDefaultLatLng() {
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(getActivity());
Track track = myTracksProviderUtils.getLastTrack();
if (track != null) {
Location location = myTracksProviderUtils.getLastValidTrackPoint(track.getId());
if (location != null) {
return new LatLng(location.getLatitude(), location.getLongitude());
}
}
return new LatLng(DEFAULT_LATITUDE, DEFAULT_LONGITUDE);
}
/**
* Returns true if the location is visible. Needs to run on the UI thread.
*
* @param location the location
*/
private boolean isLocationVisible(Location location) {
if (location == null || googleMap == null) {
return false;
}
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
return googleMap.getProjection().getVisibleRegion().latLngBounds.contains(latLng);
}
}
@@ -0,0 +1,292 @@
/*
* 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.fragments;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.EnumSet;
/**
* A fragment to display track statistics to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatsFragment extends Fragment implements TrackDataListener {
public static final String STATS_FRAGMENT_TAG = "statsFragment";
private static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute
private static final int ONE_SECOND = 1000;
private TrackDataHub trackDataHub;
private Handler handler;
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private int minRequiredAccuracy = PreferencesUtils.MIN_REQUIRED_ACCURACY_DEFAULT;
// A runnable to update the total time field.
private final Runnable updateTotalTime = new Runnable() {
public void run() {
if (isResumed() && isSelectedTrackRecording()) {
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis()
- lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
}
handler.postDelayed(this, ONE_SECOND);
}
}
};
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.stats, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
handler = new Handler();
updateUi(getActivity());
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
handler.removeCallbacks(updateTotalTime);
}
@Override
public void onLocationStateChanged(LocationState state) {
// We don't care.
}
@Override
public void onLocationChanged(final Location location) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track) {
if (isResumed()) {
handler.removeCallbacks(updateTotalTime);
if (isSelectedTrackRecording()) {
handler.post(updateTotalTime);
}
}
}
@Override
public void onTrackUpdated(final Track track) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
lastTripStatistics = track != null ? track.getTripStatistics() : null;
updateUi(getActivity());
}
}
});
}
}
@Override
public void clearTrackPoints() {
lastLocation = null;
}
@Override
public void onSampledInTrackPoint(Location location) {
lastLocation = location;
}
@Override
public void onSampledOutTrackPoint(Location location) {
lastLocation = location;
}
@Override
public void onSegmentSplit(Location location) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
lastLocation = null;
}
if (lastLocation != null) {
boolean hasFix = !isLocationOld(lastLocation);
boolean hasGoodFix = lastLocation.getAccuracy() <= minRequiredAccuracy;
if (!hasFix || !hasGoodFix) {
lastLocation = null;
}
}
StatsUtils.setLocationValues(getActivity(), lastLocation, isSelectedTrackRecording());
}
}
});
}
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
@Override
public boolean onMetricUnitsChanged(final boolean metric) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
updateUi(getActivity());
}
}
});
}
return true;
}
@Override
public boolean onReportSpeedChanged(final boolean speed) {
if (isResumed()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
updateUi(getActivity());
}
}
});
}
return true;
}
@Override
public boolean onMinRequiredAccuracy(int newValue) {
minRequiredAccuracy = newValue;
return false;
}
@Override
public boolean onMinRecordingDistanceChanged(int minRecordingDistance) {
// We don't care.
return false;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK,
TrackDataType.TRACKS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE, TrackDataType.PREFERENCE));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if the selected track is recording. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackRecording() {
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
}
/**
* Returns true if the selected track is paused. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads.
*/
private synchronized boolean isSelectedTrackPaused() {
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
}
/**
* Returns true if a location is old.
*
* @param location the location
*/
private boolean isLocationOld(Location location) {
return !LocationUtils.isValidLocation(location)
|| (System.currentTimeMillis() - location.getTime() > MAX_LOCATION_AGE_MS);
}
/**
* Updates the UI.
*/
private void updateUi(FragmentActivity activity) {
StatsUtils.setTripStatisticsValues(activity, lastTripStatistics);
StatsUtils.setLocationValues(activity, lastLocation, isSelectedTrackRecording());
}
}