arg0) {
228 | adapter.swapCursor(null);
229 | setListShown(false);
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/Pref.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.ui;
18 |
19 | import android.content.Context;
20 | import android.preference.CheckBoxPreference;
21 | import android.preference.EditTextPreference;
22 | import android.preference.ListPreference;
23 | import android.preference.Preference;
24 | import android.preference.PreferenceCategory;
25 | import android.preference.PreferenceScreen;
26 |
27 | public class Pref {
28 | public static PreferenceCategory Category(Context context, PreferenceScreen root, String caption) {
29 | PreferenceCategory retval = new PreferenceCategory(context);
30 | retval.setTitle(caption);
31 | root.addPreference(retval);
32 | return retval;
33 | }
34 |
35 | public static PreferenceCategory Category(Context context, PreferenceScreen root, int caption) {
36 | PreferenceCategory retval = new PreferenceCategory(context);
37 | if (caption > 0) retval.setTitle(caption);
38 | root.addPreference(retval);
39 | return retval;
40 | }
41 |
42 | public static Preference Preference(Context context, PreferenceCategory category, String caption, String summary, boolean enabled, Preference.OnPreferenceClickListener onClick) {
43 | Preference retval = new Preference(context);
44 | retval.setTitle(caption);
45 | retval.setSummary(summary);
46 | retval.setEnabled(enabled);
47 | if (onClick != null) {
48 | retval.setOnPreferenceClickListener(onClick);
49 | }
50 | if (category != null) category.addPreference(retval);
51 | return retval;
52 | }
53 |
54 | public static Preference Preference(Context context, PreferenceCategory category, int caption, int summary, boolean enabled, Preference.OnPreferenceClickListener onClick) {
55 | Preference retval = new Preference(context);
56 | if (caption > 0) retval.setTitle(caption);
57 | if (summary > 0) retval.setSummary(summary);
58 | retval.setEnabled(enabled);
59 | if (onClick != null) {
60 | retval.setOnPreferenceClickListener(onClick);
61 | }
62 | if (category != null) category.addPreference(retval);
63 | return retval;
64 | }
65 |
66 | public static CheckBoxPreference Check(Context context, PreferenceCategory category, String caption, String summary, String key, Object defaultValue) {
67 | return Check(context, category, caption, summary, key, defaultValue, true);
68 | }
69 |
70 | public static CheckBoxPreference Check(Context context, PreferenceCategory category, String caption, String summary, String key, Object defaultValue, boolean enabled) {
71 | CheckBoxPreference retval = new CheckBoxPreference(context);
72 | retval.setTitle(caption);
73 | retval.setSummary(summary);
74 | retval.setEnabled(enabled);
75 | retval.setKey(key);
76 | retval.setDefaultValue(defaultValue);
77 | if (category != null) category.addPreference(retval);
78 | return retval;
79 | }
80 |
81 | public static CheckBoxPreference Check(Context context, PreferenceCategory category, int caption, int summary, String key, Object defaultValue) {
82 | return Check(context, category, caption, summary, key, defaultValue, true);
83 | }
84 |
85 | public static CheckBoxPreference Check(Context context, PreferenceCategory category, int caption, int summary, String key, Object defaultValue, boolean enabled) {
86 | CheckBoxPreference retval = new CheckBoxPreference(context);
87 | if (caption > 0) retval.setTitle(caption);
88 | if (summary > 0) retval.setSummary(summary);
89 | retval.setEnabled(enabled);
90 | retval.setKey(key);
91 | retval.setDefaultValue(defaultValue);
92 | if (category != null) category.addPreference(retval);
93 | return retval;
94 | }
95 |
96 | public static ListPreference List(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue, CharSequence[] entries, CharSequence[] entryValues) {
97 | return List(context, category, caption, summary, dialogCaption, key, defaultValue, entries, entryValues, true);
98 | }
99 |
100 | public static ListPreference List(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue, CharSequence[] entries, CharSequence[] entryValues, boolean enabled) {
101 | ListPreference retval = new ListPreference(context);
102 | retval.setTitle(caption);
103 | retval.setSummary(summary);
104 | retval.setEnabled(enabled);
105 | retval.setKey(key);
106 | retval.setDefaultValue(defaultValue);
107 | retval.setDialogTitle(dialogCaption);
108 | retval.setEntries(entries);
109 | retval.setEntryValues(entryValues);
110 | if (category != null) category.addPreference(retval);
111 | return retval;
112 | }
113 |
114 | public static ListPreference List(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, CharSequence[] entries, CharSequence[] entryValues) {
115 | return List(context, category, caption, summary, dialogCaption, key, defaultValue, entries, entryValues, true);
116 | }
117 |
118 | public static ListPreference List(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, CharSequence[] entries, CharSequence[] entryValues, boolean enabled) {
119 | ListPreference retval = new ListPreference(context);
120 | if (caption > 0) retval.setTitle(caption);
121 | if (summary > 0) retval.setSummary(summary);
122 | retval.setEnabled(enabled);
123 | retval.setKey(key);
124 | retval.setDefaultValue(defaultValue);
125 | if (dialogCaption > 0) retval.setDialogTitle(dialogCaption);
126 | retval.setEntries(entries);
127 | retval.setEntryValues(entryValues);
128 | if (category != null) category.addPreference(retval);
129 | return retval;
130 | }
131 |
132 | public static EditTextPreference Edit(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue) {
133 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, true, null);
134 | }
135 |
136 | public static EditTextPreference Edit(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue, boolean enabled) {
137 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, enabled, null);
138 | }
139 |
140 | public static EditTextPreference Edit(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue, Integer type) {
141 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, true, type);
142 | }
143 |
144 | public static EditTextPreference Edit(Context context, PreferenceCategory category, String caption, String summary, String dialogCaption, String key, Object defaultValue, boolean enabled, Integer type) {
145 | EditTextPreference retval = new EditTextPreference(context);
146 | retval.setTitle(caption);
147 | retval.setSummary(summary);
148 | retval.setEnabled(enabled);
149 | retval.setKey(key);
150 | retval.setDefaultValue(defaultValue);
151 | retval.setDialogTitle(dialogCaption);
152 | if (type != null) {
153 | retval.getEditText().setInputType(type);
154 | }
155 | if (category != null) category.addPreference(retval);
156 | return retval;
157 | }
158 |
159 | public static EditTextPreference Edit(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue) {
160 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, true, null);
161 | }
162 |
163 | public static EditTextPreference Edit(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, boolean enabled) {
164 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, enabled, null);
165 | }
166 |
167 | public static EditTextPreference Edit(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, Integer type) {
168 | return Edit(context, category, caption, summary, dialogCaption, key, defaultValue, true, type);
169 | }
170 |
171 | public static EditTextPreference Edit(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, boolean enabled, Integer type) {
172 | EditTextPreference retval = new EditTextPreference(context);
173 | if (caption > 0) retval.setTitle(caption);
174 | if (summary > 0) retval.setSummary(summary);
175 | retval.setEnabled(enabled);
176 | retval.setKey(key);
177 | retval.setDefaultValue(defaultValue);
178 | if (dialogCaption > 0) retval.setDialogTitle(dialogCaption);
179 | if (type != null) {
180 | retval.getEditText().setInputType(type);
181 | }
182 | if (category != null) category.addPreference(retval);
183 | return retval;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/PreferenceListFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | * Copyright (C) 2010 The Android Open Source Project
4 | *
5 | * (based on AOSP PreferenceFragment.java with some mods)
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | package eu.chainfire.geolog.ui;
21 |
22 | import java.lang.reflect.Constructor;
23 | import java.lang.reflect.Method;
24 |
25 | import android.annotation.SuppressLint;
26 | import android.app.Activity;
27 | import android.content.Context;
28 | import android.content.Intent;
29 | import android.os.Bundle;
30 | import android.os.Handler;
31 | import android.os.Message;
32 | import android.preference.Preference;
33 | import android.preference.PreferenceManager;
34 | import android.preference.PreferenceScreen;
35 | import android.support.v4.app.ListFragment;
36 | import android.view.View;
37 | import android.widget.ListView;
38 |
39 | public abstract class PreferenceListFragment extends ListFragment {
40 | private PreferenceManager mPreferenceManager;
41 |
42 | /**
43 | * The starting request code given out to preference framework.
44 | */
45 | private static final int FIRST_REQUEST_CODE = 100;
46 |
47 | private static final int MSG_BIND_PREFERENCES = 0;
48 | @SuppressLint("HandlerLeak")
49 | private Handler mHandler = new Handler() {
50 | @Override
51 | public void handleMessage(Message msg) {
52 | switch (msg.what) {
53 |
54 | case MSG_BIND_PREFERENCES:
55 | bindPreferences();
56 | break;
57 | }
58 | }
59 | };
60 | private ListView lv = null;
61 | private int xmlId;
62 |
63 | public PreferenceListFragment(int xmlId){
64 | this.xmlId = xmlId;
65 | }
66 | //must be provided
67 | public PreferenceListFragment(){
68 |
69 | }
70 |
71 | @Override
72 | public void onDestroyView(){
73 | super.onDestroyView();
74 | /*
75 | * yada, breaks animation
76 | * ViewParent p = lv.getParent();
77 | if(p != null)
78 | ((ViewGroup)p).removeView(lv);
79 | */
80 | }
81 |
82 | @Override
83 | public void onCreate(Bundle b) {
84 | super.onCreate(b);
85 | mPreferenceManager = onCreatePreferenceManager();
86 | }
87 |
88 | @Override
89 | public void onViewCreated(View view, Bundle savedInstanceState) {
90 | super.onViewCreated(view, savedInstanceState);
91 |
92 | lv = getListView();
93 | lv.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
94 | setListShown(true);
95 |
96 | PreferenceScreen screen = createPreferenceHierarchy(mPreferenceManager);
97 | if (screen != null) setPreferenceScreen(screen);
98 | postBindPreferences();
99 |
100 | //((OnPreferenceAttachedListener)getActivity()).onPreferenceAttached(getPreferenceScreen(), 0);
101 | }
102 |
103 | protected abstract PreferenceScreen createPreferenceHierarchy(PreferenceManager prefMan);
104 |
105 | @Override
106 | public void onStop(){
107 | super.onStop();
108 | try{
109 | Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
110 | m.setAccessible(true);
111 | m.invoke(mPreferenceManager);
112 | }catch(Exception e){
113 | e.printStackTrace();
114 | }
115 | }
116 |
117 | @Override
118 | public void onDestroy() {
119 | super.onDestroy();
120 | lv = null;
121 | try{
122 | Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
123 | m.setAccessible(true);
124 | m.invoke(mPreferenceManager);
125 | }catch(Exception e){
126 | e.printStackTrace();
127 | }
128 | }
129 |
130 | @Override
131 | public void onSaveInstanceState(Bundle outState) {
132 | outState.putInt("xml", xmlId);
133 | super.onSaveInstanceState(outState);
134 |
135 | }
136 |
137 | @Override
138 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
139 | super.onActivityResult(requestCode, resultCode, data);
140 | try{
141 | Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
142 | m.setAccessible(true);
143 | m.invoke(mPreferenceManager, requestCode, resultCode, data);
144 | }catch(Exception e){
145 | e.printStackTrace();
146 | }
147 | }
148 |
149 | /**
150 | * Posts a message to bind the preferences to the list view.
151 | *
152 | * Binding late is preferred as any custom preference types created in
153 | * {@link #onCreate(Bundle)} are able to have their views recycled.
154 | */
155 | private void postBindPreferences() {
156 | if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
157 | mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
158 | }
159 |
160 | private void bindPreferences() {
161 | final PreferenceScreen preferenceScreen = getPreferenceScreen();
162 | if (preferenceScreen != null) {
163 | try {
164 | if (lv == null) lv = getListView();
165 | preferenceScreen.bind(lv);
166 | } catch (Exception e) {
167 | }
168 | }
169 | }
170 |
171 | /**
172 | * Creates the {@link PreferenceManager}.
173 | *
174 | * @return The {@link PreferenceManager} used by this activity.
175 | */
176 | private PreferenceManager onCreatePreferenceManager() {
177 | try{
178 | Constructor c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
179 | c.setAccessible(true);
180 | PreferenceManager preferenceManager = c.newInstance(this.getActivity(), FIRST_REQUEST_CODE);
181 | return preferenceManager;
182 | }catch(Exception e){
183 | e.printStackTrace();
184 | return null;
185 | }
186 | }
187 |
188 | /**
189 | * Returns the {@link PreferenceManager} used by this activity.
190 | * @return The {@link PreferenceManager}.
191 | */
192 | public PreferenceManager getPreferenceManager() {
193 | return mPreferenceManager;
194 | }
195 |
196 | /**
197 | * Sets the root of the preference hierarchy that this activity is showing.
198 | *
199 | * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
200 | */
201 | public void setPreferenceScreen(PreferenceScreen preferenceScreen){
202 | try{
203 | Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
204 | m.setAccessible(true);
205 | boolean result = (Boolean) m.invoke(mPreferenceManager, preferenceScreen);
206 | if (result && preferenceScreen != null) {
207 | postBindPreferences();
208 | }
209 | }catch(Exception e){
210 | e.printStackTrace();
211 | }
212 | }
213 |
214 | /**
215 | * Gets the root of the preference hierarchy that this activity is showing.
216 | *
217 | * @return The {@link PreferenceScreen} that is the root of the preference
218 | * hierarchy.
219 | */
220 | public PreferenceScreen getPreferenceScreen(){
221 | try{
222 | Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
223 | m.setAccessible(true);
224 | return (PreferenceScreen) m.invoke(mPreferenceManager);
225 | }catch(Exception e){
226 | e.printStackTrace();
227 | return null;
228 | }
229 | }
230 |
231 | /**
232 | * Adds preferences from activities that match the given {@link Intent}.
233 | *
234 | * @param intent The {@link Intent} to query activities.
235 | */
236 | public void addPreferencesFromIntent(Intent intent) {
237 | throw new RuntimeException("too lazy to include this bs");
238 | }
239 |
240 | /**
241 | * Inflates the given XML resource and adds the preference hierarchy to the current
242 | * preference hierarchy.
243 | *
244 | * @param preferencesResId The XML resource ID to inflate.
245 | */
246 | public void addPreferencesFromResource(int preferencesResId) {
247 | try{
248 | Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
249 | m.setAccessible(true);
250 | PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(mPreferenceManager, getActivity(), preferencesResId, getPreferenceScreen());
251 | setPreferenceScreen(prefScreen);
252 | }catch(Exception e){
253 | e.printStackTrace();
254 | }
255 | }
256 |
257 | /**
258 | * Finds a {@link Preference} based on its key.
259 | *
260 | * @param key The key of the preference to retrieve.
261 | * @return The {@link Preference} with the key, or null.
262 | * @see PreferenceGroup#findPreference(CharSequence)
263 | */
264 | public Preference findPreference(CharSequence key) {
265 | if (mPreferenceManager == null) {
266 | return null;
267 | }
268 | return mPreferenceManager.findPreference(key);
269 | }
270 |
271 | public interface OnPreferenceAttachedListener {
272 | public void onPreferenceAttached(PreferenceScreen root, int xmlId);
273 | }
274 |
275 | }
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.ui;
18 |
19 | import java.util.Locale;
20 |
21 | import com.google.android.gms.common.ConnectionResult;
22 | import com.google.android.gms.common.GooglePlayServicesUtil;
23 |
24 | import eu.chainfire.geolog.R;
25 | import eu.chainfire.geolog.data.Database;
26 | import eu.chainfire.geolog.service.BackgroundService;
27 |
28 | import android.app.ActionBar;
29 | import android.app.AlertDialog;
30 | import android.app.FragmentTransaction;
31 | import android.app.ProgressDialog;
32 | import android.content.DialogInterface;
33 | import android.content.SharedPreferences;
34 | import android.content.DialogInterface.OnClickListener;
35 | import android.database.Cursor;
36 | import android.os.AsyncTask;
37 | import android.os.Bundle;
38 | import android.preference.PreferenceManager;
39 | import android.support.v4.app.Fragment;
40 | import android.support.v4.app.FragmentActivity;
41 | import android.support.v4.app.FragmentManager;
42 | import android.support.v4.app.FragmentPagerAdapter;
43 | import android.support.v4.view.ViewPager;
44 | import android.text.Html;
45 | import android.view.Menu;
46 | import android.view.MenuItem;
47 | import android.view.MenuItem.OnMenuItemClickListener;
48 |
49 | public class MainActivity extends FragmentActivity implements
50 | ActionBar.TabListener {
51 |
52 | private ProfilesFragment tabProfiles = null;
53 | private LogsFragment tabLogs = null;
54 | private SettingsFragment tabSettings = null;
55 |
56 | private SectionsPagerAdapter mSectionsPagerAdapter;
57 | private ViewPager mViewPager;
58 |
59 | @Override
60 | protected void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 | setContentView(R.layout.activity_main);
63 |
64 | final ActionBar actionBar = getActionBar();
65 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
66 |
67 | mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
68 | mViewPager = (ViewPager) findViewById(R.id.pager);
69 | mViewPager.setAdapter(mSectionsPagerAdapter);
70 |
71 | mViewPager
72 | .setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
73 | @Override
74 | public void onPageSelected(int position) {
75 | actionBar.setSelectedNavigationItem(position);
76 | invalidateOptionsMenu();
77 | }
78 | });
79 | mViewPager.setOffscreenPageLimit(3);
80 |
81 | for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
82 | actionBar.addTab(actionBar.newTab()
83 | .setText(mSectionsPagerAdapter.getPageTitle(i))
84 | .setTabListener(this));
85 | }
86 |
87 | int play = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
88 | if (play == ConnectionResult.SUCCESS) {
89 | // Force database creation
90 | Database.Helper helper = Database.Helper.getInstance(this);
91 | helper.getReadableDatabase();
92 |
93 | // Force Off profile as default
94 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
95 | long id = prefs.getLong(SettingsFragment.PREF_CURRENT_PROFILE, 0);
96 | Database.Profile profile = Database.Profile.getById(helper, id, null);
97 | if (profile == null) id = 0;
98 | if (id == 0) {
99 | profile = Database.Profile.getOffProfile(helper);
100 | id = profile.getId();
101 | prefs.edit().putLong(SettingsFragment.PREF_CURRENT_PROFILE, id).commit();
102 | }
103 |
104 | // Start background service
105 | if (profile.getType() != Database.Profile.Type.OFF) BackgroundService.startService(this);
106 | } else {
107 | GooglePlayServicesUtil.getErrorDialog(play, this, 0).show();
108 | finish();
109 | }
110 | }
111 |
112 | private void addProfile() {
113 | Cursor list = Database.Profile.list(Database.Helper.getInstance(this));
114 |
115 | final CharSequence[] items = new CharSequence[list.getCount() + 1];
116 | final Long[] ids = new Long[list.getCount() + 1];
117 |
118 | items[0] = Html.fromHtml(getString(R.string.profile_add_name));
119 | ids[0] = 0L;
120 |
121 | int i = 1;
122 | int idxID = list.getColumnIndex(Database.Profile._ID);
123 | int idxName = list.getColumnIndex(Database.Profile.COLUMN_NAME_NAME);
124 | if (list.moveToFirst()) {
125 | while (true) {
126 | ids[i] = list.getLong(idxID);
127 | items[i] = Html.fromHtml(String.format(Locale.ENGLISH, getString(R.string.profile_add_copy), list.getString(idxName)));
128 | i++;
129 | if (!list.moveToNext()) break;
130 | }
131 | }
132 |
133 | (new AlertDialog.Builder(this)).
134 | setTitle(R.string.profile_add_title).
135 | setItems(items, new OnClickListener() {
136 | @Override
137 | public void onClick(DialogInterface dialog, int which) {
138 | Database.Helper h = Database.Helper.getInstance(MainActivity.this);
139 | if (which == 0) {
140 | Database.Profile p = new Database.Profile();
141 | p.setName(getString(R.string.profile_add_name));
142 | p.setType(Database.Profile.Type.USER);
143 | p.saveToDatabase(h);
144 | ProfileActivity.launchActivity(MainActivity.this, p.getId());
145 | } else {
146 | ProfileActivity.launchActivity(MainActivity.this, Database.Profile.copy(h, Database.Profile.getById(h, ids[which], null), getString(R.string.profile_add_name)).getId());
147 | }
148 | }
149 | }).
150 | setCancelable(true).
151 | setNegativeButton(getString(R.string.generic_cancel), null).
152 | show();
153 | }
154 |
155 | @Override
156 | public boolean onCreateOptionsMenu(Menu menu) {
157 | int tab = getActionBar().getSelectedNavigationIndex();
158 |
159 | if (tab == 0) {
160 | menu.
161 | add(R.string.menu_add).
162 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
163 | @Override
164 | public boolean onMenuItemClick(MenuItem item) {
165 | addProfile();
166 | return true;
167 | }
168 | }).
169 | setIcon(R.drawable.ic_action_add).
170 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
171 | } else if (tab == 1) {
172 | menu.
173 | add(R.string.menu_export).
174 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
175 | @Override
176 | public boolean onMenuItemClick(MenuItem item) {
177 | ExportActivity.launchActivity(MainActivity.this, 0);
178 | return true;
179 | }
180 | }).
181 | setIcon(R.drawable.ic_action_export).
182 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
183 | menu.
184 | add(R.string.menu_clear).
185 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
186 | @Override
187 | public boolean onMenuItemClick(MenuItem item) {
188 | (new AlertDialog.Builder(MainActivity.this)).
189 | setTitle(R.string.generic_clear).
190 | setMessage(Html.fromHtml(getString(R.string.logs_clear_confirm))).
191 | setPositiveButton(getString(R.string.generic_clear), new DialogInterface.OnClickListener() {
192 | @Override
193 | public void onClick(DialogInterface dialog, int which) {
194 | (new ClearLogsAsync()).execute();
195 | }
196 | }).
197 | setNegativeButton(getString(R.string.generic_cancel), null).
198 | show();
199 |
200 | return true;
201 | }
202 | }).
203 | setIcon(R.drawable.ic_action_delete).
204 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
205 |
206 | } else if (tab == 2) {
207 |
208 | }
209 |
210 | return true;
211 | }
212 |
213 | @Override
214 | public void onTabSelected(ActionBar.Tab tab,
215 | FragmentTransaction fragmentTransaction) {
216 | mViewPager.setCurrentItem(tab.getPosition());
217 | }
218 |
219 | @Override
220 | public void onTabUnselected(ActionBar.Tab tab,
221 | FragmentTransaction fragmentTransaction) {
222 | }
223 |
224 | @Override
225 | public void onTabReselected(ActionBar.Tab tab,
226 | FragmentTransaction fragmentTransaction) {
227 | }
228 |
229 | public class SectionsPagerAdapter extends FragmentPagerAdapter {
230 |
231 | public SectionsPagerAdapter(FragmentManager fm) {
232 | super(fm);
233 | }
234 |
235 | @Override
236 | public Fragment getItem(int position) {
237 | if (position == 0) {
238 | if (tabProfiles == null) tabProfiles = new ProfilesFragment();
239 | return tabProfiles;
240 | } else if (position == 1) {
241 | if (tabLogs == null) tabLogs = new LogsFragment();
242 | return tabLogs;
243 | } else if (position == 2) {
244 | if (tabSettings == null) tabSettings = new SettingsFragment();
245 | return tabSettings;
246 | }
247 | return null;
248 | }
249 |
250 | @Override
251 | public int getCount() {
252 | return 3;
253 | }
254 |
255 | @Override
256 | public CharSequence getPageTitle(int position) {
257 | Locale l = Locale.getDefault();
258 | switch (position) {
259 | case 0: return getString(R.string.section_profiles).toUpperCase(l);
260 | case 1: return getString(R.string.section_logs).toUpperCase(l);
261 | case 2: return getString(R.string.section_settings).toUpperCase(l);
262 | }
263 | return null;
264 | }
265 | }
266 |
267 | private class ClearLogsAsync extends AsyncTask {
268 | private ProgressDialog dialog = null;
269 |
270 | @Override
271 | protected void onPreExecute() {
272 | dialog = new ProgressDialog(MainActivity.this);
273 | dialog.setMessage(getString(R.string.logs_clear_clearing));
274 | dialog.setIndeterminate(true);
275 | dialog.setCancelable(false);
276 | dialog.show();
277 | }
278 |
279 | @Override
280 | protected Void doInBackground(Void... params) {
281 | Database.Location.deleteAll(Database.Helper.getInstance(MainActivity.this));
282 | return null;
283 | }
284 |
285 | @Override
286 | protected void onPostExecute(Void result) {
287 | dialog.dismiss();
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.ui;
18 |
19 | import java.util.Locale;
20 |
21 | import eu.chainfire.geolog.R;
22 |
23 | import android.annotation.SuppressLint;
24 | import android.app.AlertDialog;
25 | import android.content.DialogInterface;
26 | import android.content.Intent;
27 | import android.content.SharedPreferences;
28 | import android.content.DialogInterface.OnClickListener;
29 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
30 | import android.content.pm.PackageInfo;
31 | import android.content.pm.PackageManager;
32 | import android.content.res.Resources;
33 | import android.net.Uri;
34 | import android.os.Bundle;
35 | import android.preference.ListPreference;
36 | import android.preference.Preference;
37 | import android.preference.PreferenceCategory;
38 | import android.preference.PreferenceManager;
39 | import android.preference.PreferenceScreen;
40 | import android.preference.Preference.OnPreferenceClickListener;
41 | import android.support.v4.content.LocalBroadcastManager;
42 | import android.view.View;
43 |
44 | @SuppressLint("NewApi")
45 | public class
46 | SettingsFragment
47 | extends
48 | PreferenceListFragment
49 | implements
50 | OnSharedPreferenceChangeListener
51 | {
52 | public static final String APP_TITLE = "GeoLog";
53 | public static final String APP_COPYRIGHT = "Copyright (C) 2013 - Chainfire";
54 | public static final String APP_WEBSITE_URL = "http://forum.xda-developers.com/showthread.php?t=2386317";
55 |
56 | public static final String NOTIFY_BROADCAST = "eu.chainfire.geolog.PREFERENCES.UPDATED";
57 | public static final String EXTRA_KEY = "eu.chainfire.geolog.EXTRA.KEY";
58 |
59 | public static final String PREF_FOLLOW_SHOWN = "follow_shown";
60 | public static final String PREF_CURRENT_PROFILE = "current_profile";
61 |
62 | public static final float METER_FEET_RATIO = 3.28084f;
63 | public static final String PREF_UNITS = "pref_units";
64 | public static final String VALUE_UNITS_METRIC = "metric";
65 | public static final String VALUE_UNITS_IMPERIAL = "imperial";
66 | public static final String PREF_UNITS_DEFAULT = VALUE_UNITS_METRIC;
67 |
68 | private SharedPreferences prefs = null;
69 |
70 | private Resources resources = null;
71 |
72 | private String S(int id) { return resources.getString(id); }
73 | @SuppressWarnings("unused")
74 | private String[] SA(int id) { return resources.getStringArray(id); }
75 |
76 | private ListPreference prefUnits = null;
77 |
78 | @Override
79 | protected PreferenceScreen createPreferenceHierarchy(PreferenceManager prefMan) {
80 | PreferenceScreen root = prefMan.createPreferenceScreen(getActivity());
81 |
82 | resources = getActivity().getResources();
83 |
84 | prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
85 |
86 | String app = APP_TITLE;
87 | PackageManager pm = getActivity().getPackageManager();
88 | if (pm != null) {
89 | try {
90 | PackageInfo pi = pm.getPackageInfo(getActivity().getPackageName(), 0);
91 | if (pi != null) {
92 | app = app + " v" + pi.versionName;
93 | }
94 | } catch (Exception e) {
95 | }
96 | }
97 |
98 | Preference copyright = new Preference(getActivity());
99 | copyright.setTitle(app);
100 | copyright.setSummary(
101 | APP_COPYRIGHT + (char)10 +
102 | "Twitter: @ChainfireXDA" + (char)10 +
103 | "G+: http://google.com/+Chainfire" + (char)10 +
104 | S(R.string.settings_tap_xda)
105 | );
106 | copyright.setKey("copyright");
107 | copyright.setEnabled(true);
108 | copyright.setOnPreferenceClickListener(new OnPreferenceClickListener() {
109 | public boolean onPreferenceClick(Preference preference) {
110 | try {
111 | Intent i = new Intent(Intent.ACTION_VIEW);
112 | i.setData(Uri.parse(APP_WEBSITE_URL));
113 | startActivity(i);
114 | } catch (Exception e) {
115 | // no http handler installed (wtf, it happens)
116 | }
117 | return false;
118 | }
119 | });
120 | root.addPreference(copyright);
121 |
122 | /* maybe one day
123 | if (!proPresent) {
124 | Preference upgrade = new Preference(getActivity());
125 | upgrade.setTitle(R.string.settings_upgrade);
126 | upgrade.setSummary(R.string.settings_upgrade_description);
127 | upgrade.setOnPreferenceClickListener(new OnPreferenceClickListener() {
128 | public boolean onPreferenceClick(Preference preference) {
129 | try {
130 | Intent i = new Intent(Intent.ACTION_VIEW);
131 | i.setData(Uri.parse("market://details?id=eu.chainfire.geolog.pro"));
132 | startActivity(i);
133 | } catch (Exception e) {
134 | // no market installed
135 | }
136 | return false;
137 | }
138 | });
139 | root.addPreference(upgrade);
140 | }
141 | */
142 |
143 | PreferenceCategory catUnits = Pref.Category(getActivity(), root, R.string.settings_category_units);
144 | prefUnits = Pref.List(
145 | getActivity(),
146 | catUnits,
147 | getString(R.string.settings_units_caption),
148 | "",
149 | getString(R.string.settings_units_popup),
150 | PREF_UNITS,
151 | PREF_UNITS_DEFAULT,
152 | new String[] {
153 | getString(R.string.settings_units_metric),
154 | getString(R.string.settings_units_imperial)
155 | },
156 | new String[] {
157 | VALUE_UNITS_METRIC,
158 | VALUE_UNITS_IMPERIAL
159 | }
160 | );
161 |
162 | PreferenceCategory catMarket = Pref.Category(getActivity(), root, R.string.settings_category_market);
163 | Pref.Preference(getActivity(), catMarket, R.string.settings_market, R.string.settings_market_description, true, new OnPreferenceClickListener() {
164 | public boolean onPreferenceClick(Preference preference) {
165 | try {
166 | Intent i = new Intent(Intent.ACTION_VIEW);
167 | i.setData(Uri.parse("market://search?q=pub:Chainfire"));
168 | startActivity(i);
169 | } catch (Exception e) {
170 | // market not installed
171 | }
172 | return false;
173 | }
174 | });
175 |
176 | Pref.Preference(getActivity(), catMarket, R.string.follow_pref_title, R.string.follow_pref_desc, true, new OnPreferenceClickListener() {
177 | @Override
178 | public boolean onPreferenceClick(Preference preference) {
179 | showFollow(false);
180 | return false;
181 | }
182 | });
183 |
184 | int shown_follow = prefs.getInt(PREF_FOLLOW_SHOWN, 0);
185 | if (shown_follow == 0) {
186 | prefs.edit().putInt(PREF_FOLLOW_SHOWN, 1).commit();
187 | showFollow(true);
188 | }
189 |
190 | updatePrefs(null);
191 | return root;
192 | }
193 |
194 | private void showFollow(boolean startup) {
195 | if (startup) {
196 | AlertDialog.Builder builder = (new AlertDialog.Builder(getActivity())).
197 | setTitle(R.string.follow_popup_title).
198 | setMessage(R.string.follow_popup_desc).
199 | setPositiveButton(R.string.follow_twitter, new OnClickListener() {
200 | @Override
201 | public void onClick(DialogInterface dialog, int which) {
202 | Intent i = new Intent(Intent.ACTION_VIEW);
203 | i.setData(Uri.parse("http://www.twitter.com/ChainfireXDA"));
204 | startActivity(i);
205 | }
206 | }).
207 | setNeutralButton(R.string.follow_gplus, new OnClickListener() {
208 | @Override
209 | public void onClick(DialogInterface dialog, int which) {
210 | Intent i = new Intent(Intent.ACTION_VIEW);
211 | i.setData(Uri.parse("http://google.com/+Chainfire"));
212 | startActivity(i);
213 | }
214 | }).
215 | setNegativeButton(R.string.follow_nothanks, new OnClickListener() {
216 | @Override
217 | public void onClick(DialogInterface dialog, int which) {
218 | }
219 | });
220 | try {
221 | builder.show();
222 | } catch (Exception e) {
223 | }
224 | } else {
225 | AlertDialog.Builder builder = (new AlertDialog.Builder(getActivity())).
226 | setTitle(R.string.follow_popup_title).
227 | setItems(new CharSequence[] { S(R.string.follow_twitter), S(R.string.follow_gplus) }, new OnClickListener() {
228 | @Override
229 | public void onClick(DialogInterface dialog, int which) {
230 | if (which == 0) {
231 | Intent i = new Intent(Intent.ACTION_VIEW);
232 | i.setData(Uri.parse("http://www.twitter.com/ChainfireXDA"));
233 | startActivity(i);
234 | } else if (which == 1) {
235 | Intent i = new Intent(Intent.ACTION_VIEW);
236 | i.setData(Uri.parse("http://plus.google.com/b/113517319477420052449/"));
237 | startActivity(i);
238 | }
239 | }
240 | }).
241 | setNegativeButton(R.string.generic_close, null);
242 | try {
243 | builder.show();
244 | } catch (Exception e) {
245 | }
246 | }
247 | }
248 |
249 | @Override
250 | public void onViewCreated(View view, Bundle savedInstanceState) {
251 | super.onViewCreated(view, savedInstanceState);
252 | prefs.registerOnSharedPreferenceChangeListener(this);
253 | }
254 |
255 |
256 | @Override
257 | public void onDestroyView() {
258 | prefs.unregisterOnSharedPreferenceChangeListener(this);
259 | super.onDestroyView();
260 | }
261 |
262 | public void updatePrefs(String key) {
263 | try {
264 | if ((key == null) || (key.equals(PREF_UNITS))) {
265 | int id = R.string.settings_units_metric;
266 | if (prefs.getString(PREF_UNITS, PREF_UNITS_DEFAULT).equals(VALUE_UNITS_IMPERIAL)) id = R.string.settings_units_imperial;
267 | prefUnits.setSummary(String.format(Locale.ENGLISH, "[ %s ]", getString(id)));
268 | }
269 |
270 | Intent i = new Intent(NOTIFY_BROADCAST);
271 | i.putExtra(EXTRA_KEY, key);
272 | LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(i);
273 | } catch (Exception e) {
274 | }
275 | }
276 |
277 | @Override
278 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
279 | updatePrefs(key);
280 | }
281 | }
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/ProfileActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.ui;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Locale;
22 |
23 | import eu.chainfire.geolog.R;
24 | import eu.chainfire.geolog.data.Database;
25 | import eu.chainfire.geolog.data.Database.Profile.ActivitySettings;
26 |
27 | import android.app.Activity;
28 | import android.content.Intent;
29 | import android.os.Bundle;
30 | import android.preference.EditTextPreference;
31 | import android.preference.ListPreference;
32 | import android.preference.Preference;
33 | import android.preference.Preference.OnPreferenceChangeListener;
34 | import android.preference.PreferenceActivity;
35 | import android.preference.PreferenceCategory;
36 | import android.preference.PreferenceScreen;
37 | import android.text.InputType;
38 | import android.view.Menu;
39 | import android.view.MenuItem;
40 | import android.view.MenuItem.OnMenuItemClickListener;
41 |
42 | @SuppressWarnings("deprecation")
43 | public class ProfileActivity extends PreferenceActivity {
44 | public static final String EXTRA_PROFILE_ID = "eu.chainfire.geolog.ProfileActivity.EXTRA.PROFILE_ID";
45 |
46 | public static void launchActivity(Activity activity, long id) {
47 | Intent i = new Intent(activity, ProfileActivity.class);
48 | i.putExtra(ProfileActivity.EXTRA_PROFILE_ID, id);
49 | activity.startActivityForResult(i, 0);
50 | }
51 |
52 | private Database.Helper helper = null;
53 | private Database.Profile profile = null;
54 | private List dependents = new ArrayList();
55 | private EditTextPreference prefActivityIntervalStill = null;
56 | private EditTextPreference prefActivityIntervalFoot = null;
57 | private EditTextPreference prefActivityIntervalBicycle = null;
58 | private EditTextPreference prefActivityIntervalVehicle = null;
59 |
60 | @Override
61 | protected void onCreate(Bundle savedInstanceState) {
62 | super.onCreate(savedInstanceState);
63 | setResult(RESULT_OK);
64 |
65 | long id = 0;
66 | if (getIntent() != null) {
67 | id = getIntent().getLongExtra(EXTRA_PROFILE_ID, 0);
68 | } else if (savedInstanceState != null) {
69 | id = savedInstanceState.getLong(EXTRA_PROFILE_ID, 0);
70 | }
71 | if (id == 0) {
72 | finish();
73 | } else {
74 | helper = Database.Helper.getInstance(this);
75 | profile = Database.Profile.getById(Database.Helper.getInstance(this), id, null);
76 | setTitle(profile.getName());
77 | setPreferenceScreen(createPreferenceHierarchy());
78 | }
79 | }
80 |
81 | @Override
82 | public boolean onCreateOptionsMenu(Menu menu) {
83 | menu.
84 | add(R.string.menu_done).
85 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
86 | @Override
87 | public boolean onMenuItemClick(MenuItem item) {
88 | finish();
89 | return false;
90 | }
91 | }).
92 | setIcon(R.drawable.ic_action_done).
93 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
94 | return true;
95 | }
96 |
97 | @Override
98 | protected void onSaveInstanceState(Bundle outState) {
99 | outState.putLong(EXTRA_PROFILE_ID, profile.getId());
100 | super.onSaveInstanceState(outState);
101 | }
102 |
103 | @Override
104 | protected void onUserLeaveHint() {
105 | finish();
106 | super.onUserLeaveHint();
107 | }
108 |
109 | @Override
110 | public void onBackPressed() {
111 | finish();
112 | super.onBackPressed();
113 | }
114 |
115 | private String formatValue(String value) {
116 | return String.format(Locale.ENGLISH, "[ %s ]", value);
117 | }
118 |
119 | private String formatValue(int interval) {
120 | if (interval == 0) return formatValue(getString(R.string.profile_preference_format_disabled));
121 |
122 | int hours = interval / 3600;
123 | interval %= 3600;
124 | int minutes = interval / 60;
125 | interval %= 60;
126 | int seconds = interval;
127 |
128 | StringBuilder b = new StringBuilder();
129 | if (hours > 0) {
130 | b.append(String.format(Locale.ENGLISH, "%d:%02d:%02d", hours, minutes, seconds));
131 | } else {
132 | b.append(String.format(Locale.ENGLISH, "%d:%02d", minutes, seconds));
133 | }
134 | b.append(" - ");
135 | if (hours > 0) {
136 | b.append(String.format(Locale.ENGLISH, getString(R.string.profile_preference_format_interval_hours), hours, minutes, seconds));
137 | } else {
138 | b.append(String.format(Locale.ENGLISH, getString(R.string.profile_preference_format_interval), minutes, seconds));
139 | }
140 |
141 | return formatValue(b.toString());
142 | }
143 |
144 | private int getActivityCaption(Database.Activity activity) {
145 | switch (activity) {
146 | case UNKNOWN : return R.string.profile_preference_caption_unknown;
147 | case STILL : return R.string.profile_preference_caption_still;
148 | case FOOT : return R.string.profile_preference_caption_foot;
149 | case BICYCLE : return R.string.profile_preference_caption_bicycle;
150 | case VEHICLE : return R.string.profile_preference_caption_vehicle;
151 | }
152 | return R.string.profile_preference_caption_unknown;
153 | }
154 |
155 | private String accuracyToDescription(Database.Accuracy accuracy) {
156 | switch (accuracy) {
157 | case NONE : return getString(R.string.profile_preference_accuracy_none);
158 | case LOW : return getString(R.string.profile_preference_accuracy_low);
159 | case HIGH : return getString(R.string.profile_preference_accuracy_high);
160 | }
161 | return getString(R.string.profile_preference_accuracy_none);
162 | }
163 |
164 | private String accuracyToValue(Database.Accuracy accuracy) {
165 | switch (accuracy) {
166 | case NONE : return "none";
167 | case LOW : return "low";
168 | case HIGH : return "high";
169 | }
170 | return "none";
171 | }
172 |
173 | private Database.Accuracy accuracyFromValue(String accuracy) {
174 | if ("none".equals(accuracy)) return Database.Accuracy.NONE;
175 | if ("low".equals(accuracy)) return Database.Accuracy.LOW;
176 | if ("high".equals(accuracy)) return Database.Accuracy.HIGH;
177 | return Database.Accuracy.NONE;
178 | }
179 |
180 | private void updateActivitySettings(Database.Profile.ActivitySettings settings, Preference prefAccuracy, Preference prefLocationInterval, Preference prefActivityInterval) {
181 | if (prefAccuracy != null) prefAccuracy.setSummary(formatValue(accuracyToDescription(settings.getAccuracy())));
182 | if (prefLocationInterval != null) prefLocationInterval.setSummary(formatValue(settings.getAccuracy() == Database.Accuracy.NONE ? 0 : settings.getLocationInterval()));
183 | if (prefActivityInterval != null) prefActivityInterval.setSummary(formatValue(settings.getActivityInterval()));
184 | }
185 |
186 | private void addActivitySettings(PreferenceScreen root, Database.Activity activity) {
187 | final Database.Profile.ActivitySettings settings = profile.getActivitySettings(activity);
188 |
189 | final boolean isUnknown = (activity == Database.Activity.UNKNOWN);
190 | final boolean enabled = (
191 | isUnknown ||
192 | (profile.getUnknown().getActivityInterval() != 0)
193 | );
194 |
195 | final PreferenceCategory catActivity = Pref.Category(this, root, getActivityCaption(activity));
196 | final ListPreference prefAccuracy = Pref.List(
197 | this,
198 | catActivity,
199 | getString(R.string.profile_preference_accuracy_title),
200 | "",
201 | getString(R.string.profile_preference_accuracy_popup),
202 | null,
203 | accuracyToValue(settings.getAccuracy()),
204 | new String[] {
205 | accuracyToDescription(Database.Accuracy.NONE),
206 | accuracyToDescription(Database.Accuracy.LOW),
207 | accuracyToDescription(Database.Accuracy.HIGH)
208 | },
209 | new String[] {
210 | accuracyToValue(Database.Accuracy.NONE),
211 | accuracyToValue(Database.Accuracy.LOW),
212 | accuracyToValue(Database.Accuracy.HIGH)
213 | },
214 | enabled
215 | );
216 | final EditTextPreference prefLocationInterval = Pref.Edit(
217 | this,
218 | catActivity,
219 | getString(R.string.profile_preference_location_interval_title),
220 | "",
221 | getString(R.string.profile_preference_location_interval_popup),
222 | null,
223 | String.valueOf(settings.getLocationInterval()),
224 | enabled,
225 | InputType.TYPE_CLASS_NUMBER
226 | );
227 | final EditTextPreference prefActivityInterval = Pref.Edit(
228 | this,
229 | catActivity,
230 | getString(R.string.profile_preference_activity_interval_title),
231 | "",
232 | getString(R.string.profile_preference_activity_interval_popup),
233 | null,
234 | String.valueOf(settings.getActivityInterval()),
235 | enabled,
236 | InputType.TYPE_CLASS_NUMBER
237 | );
238 |
239 | prefAccuracy.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
240 | @Override
241 | public boolean onPreferenceChange(Preference preference, Object newValue) {
242 | if ((newValue instanceof String) && (((String)newValue).length() > 0)) {
243 | Database.Accuracy val = accuracyFromValue((String)newValue);
244 | settings.setAccuracy(val);
245 | profile.saveToDatabase(helper);
246 | updateActivitySettings(settings, prefAccuracy, prefLocationInterval, prefActivityInterval);
247 | return true;
248 | }
249 | return false;
250 | }
251 | });
252 |
253 | prefLocationInterval.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
254 | @Override
255 | public boolean onPreferenceChange(Preference preference, Object newValue) {
256 | if ((newValue instanceof String) && (((String)newValue).length() > 0)) {
257 | int val = Integer.parseInt((String)newValue, 10);
258 | settings.setLocationInterval(val);
259 | profile.saveToDatabase(helper);
260 | updateActivitySettings(settings, prefAccuracy, prefLocationInterval, prefActivityInterval);
261 | return true;
262 | }
263 | return false;
264 | }
265 | });
266 |
267 | prefActivityInterval.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
268 | @Override
269 | public boolean onPreferenceChange(Preference preference, Object newValue) {
270 | if ((newValue instanceof String) && (((String)newValue).length() > 0)) {
271 | int val = Integer.parseInt((String)newValue, 10);
272 | settings.setActivityInterval(val);
273 | profile.saveToDatabase(helper);
274 | updateActivitySettings(settings, prefAccuracy, prefLocationInterval, prefActivityInterval);
275 |
276 | if (isUnknown) {
277 | boolean enabled = (val != 0);
278 | for (Preference p : dependents) {
279 | p.setEnabled(enabled);
280 | }
281 | if (enabled) {
282 | for (ActivitySettings a : new ActivitySettings[] {
283 | profile.getStill(),
284 | profile.getFoot(),
285 | profile.getBicycle(),
286 | profile.getVehicle()
287 | }) {
288 | if (a.getActivityInterval() == 0) a.setActivityInterval(val);
289 | }
290 | profile.saveToDatabase(helper);
291 |
292 | prefActivityIntervalStill.setText(String.valueOf(profile.getStill().getActivityInterval()));
293 | prefActivityIntervalFoot.setText(String.valueOf(profile.getFoot().getActivityInterval()));
294 | prefActivityIntervalBicycle.setText(String.valueOf(profile.getBicycle().getActivityInterval()));
295 | prefActivityIntervalVehicle.setText(String.valueOf(profile.getVehicle().getActivityInterval()));
296 |
297 | updateActivitySettings(profile.getStill(), null, null, prefActivityIntervalStill);
298 | updateActivitySettings(profile.getFoot(), null, null, prefActivityIntervalFoot);
299 | updateActivitySettings(profile.getBicycle(), null, null, prefActivityIntervalBicycle);
300 | updateActivitySettings(profile.getVehicle(), null, null, prefActivityIntervalVehicle);
301 | }
302 | }
303 | return true;
304 | }
305 | return false;
306 | }
307 | });
308 |
309 | updateActivitySettings(settings, prefAccuracy, prefLocationInterval, prefActivityInterval);
310 |
311 | if (activity != Database.Activity.UNKNOWN) {
312 | dependents.add(prefAccuracy);
313 | dependents.add(prefLocationInterval);
314 | dependents.add(prefActivityInterval);
315 | }
316 |
317 | switch (activity) {
318 | case STILL: prefActivityIntervalStill = prefActivityInterval; break;
319 | case FOOT: prefActivityIntervalFoot = prefActivityInterval; break;
320 | case BICYCLE: prefActivityIntervalBicycle = prefActivityInterval; break;
321 | case VEHICLE: prefActivityIntervalVehicle = prefActivityInterval; break;
322 | }
323 | }
324 |
325 | private PreferenceScreen createPreferenceHierarchy() {
326 | PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
327 |
328 | EditTextPreference prefName = new EditTextPreference(this);
329 | prefName.setTitle(R.string.profile_preference_name_title);
330 | prefName.setSummary(formatValue(profile.getName()));
331 | prefName.setDefaultValue(profile.getName());
332 | prefName.setDialogTitle(R.string.profile_preference_name_popup);
333 | prefName.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
334 | @Override
335 | public boolean onPreferenceChange(Preference preference, Object newValue) {
336 | if ((newValue instanceof String) && (((String)newValue).length() > 0)) {
337 | String val = ((String)newValue).trim();
338 | setTitle(val);
339 | preference.setSummary(formatValue(val));
340 | profile.setName(val);
341 | profile.saveToDatabase(helper);
342 | return true;
343 | }
344 | return false;
345 | }
346 | });
347 | root.addPreference(prefName);
348 |
349 | EditTextPreference prefReduceAccuracyDelay = Pref.Edit(
350 | this,
351 | null,
352 | getString(R.string.profile_preference_reduce_accuracy_delay_title),
353 | formatValue(profile.getReduceAccuracyDelay()),
354 | getString(R.string.profile_preference_reduce_accuracy_delay_popup),
355 | null,
356 | String.valueOf(profile.getReduceAccuracyDelay()),
357 | true,
358 | InputType.TYPE_CLASS_NUMBER
359 | );
360 | root.addPreference(prefReduceAccuracyDelay);
361 | prefReduceAccuracyDelay.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
362 | @Override
363 | public boolean onPreferenceChange(Preference preference, Object newValue) {
364 | if ((newValue instanceof String) && (((String)newValue).length() > 0)) {
365 | int val = Integer.parseInt((String)newValue, 10);
366 | profile.setReduceAccuracyDelay(val);
367 | profile.saveToDatabase(helper);
368 | preference.setSummary(formatValue(profile.getReduceAccuracyDelay()));
369 | return true;
370 | }
371 | return false;
372 | }
373 | });
374 |
375 | addActivitySettings(root, Database.Activity.UNKNOWN);
376 | addActivitySettings(root, Database.Activity.STILL);
377 | addActivitySettings(root, Database.Activity.FOOT);
378 | addActivitySettings(root, Database.Activity.BICYCLE);
379 | addActivitySettings(root, Database.Activity.VEHICLE);
380 |
381 | return root;
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/data/Exporter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.data;
18 |
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 | import java.io.UnsupportedEncodingException;
24 | import java.text.SimpleDateFormat;
25 | import java.util.Date;
26 | import java.util.Locale;
27 | import java.util.TimeZone;
28 |
29 | import eu.chainfire.geolog.Application;
30 | import eu.chainfire.geolog.Debug;
31 | import eu.chainfire.geolog.R;
32 | import eu.chainfire.geolog.data.Database.*;
33 |
34 | import android.app.Activity;
35 | import android.app.AlertDialog;
36 | import android.app.ProgressDialog;
37 | import android.content.Context;
38 | import android.content.pm.PackageInfo;
39 | import android.content.pm.PackageManager;
40 | import android.database.Cursor;
41 | import android.os.AsyncTask;
42 | import android.text.Html;
43 |
44 | public class Exporter {
45 | public static enum Format { GPX, KML };
46 |
47 | private interface OnExportProgressListener {
48 | public void OnExportProgress(int cur, int total);
49 | }
50 |
51 | private final Context context;
52 |
53 | private Format format = Format.GPX;
54 | private long trackMergeGap = 0;
55 | private long dateStart = -1;
56 | private long dateEnd = -1;
57 | private long trackMinPoints = 0;
58 | private long trackMinTime = 0;
59 | private long trackMinDistance = 0;
60 | private long accuracyLowPowerUnknown = 0;
61 | private long accuracyLowPowerStill = 0;
62 | private long accuracyLowPowerFoot = 0;
63 | private long accuracyLowPowerBicycle = 0;
64 | private long accuracyLowPowerVehicle = 0;
65 | private long accuracyHighAccuracyUnknown = 0;
66 | private long accuracyHighAccuracyStill = 0;
67 | private long accuracyHighAccuracyFoot = 0;
68 | private long accuracyHighAccuracyBicycle = 0;
69 | private long accuracyHighAccuracyVehicle = 0;
70 |
71 | private int inSegment = 0;
72 | private boolean isSegmentStart = false;
73 | private long lastTime = -1;
74 | private long lastTrackStartBytes = 0;
75 | private long lastTrackStartTime = 0;
76 | private double trackLatMin = 0;
77 | private double trackLatMax = 0;
78 | private double trackLongMin = 0;
79 | private double trackLongMax = 0;
80 |
81 | public Exporter(Context context) {
82 | this.context = context;
83 | }
84 |
85 | public void export(Cursor cursor) {
86 | if (context instanceof Activity) {
87 | (new ExportAsync()).execute(cursor);
88 | } else {
89 | performExport(null, cursor);
90 | }
91 | }
92 |
93 | private double gps2m(double lat_a, double lng_a, double lat_b, double lng_b) {
94 | double pk = (double) (180/Math.PI);
95 |
96 | double a1 = lat_a / pk;
97 | double a2 = lng_a / pk;
98 | double b1 = lat_b / pk;
99 | double b2 = lng_b / pk;
100 |
101 | double t1 = Math.cos(a1)*Math.cos(a2)*Math.cos(b1)*Math.cos(b2);
102 | double t2 = Math.cos(a1)*Math.sin(a2)*Math.cos(b1)*Math.sin(b2);
103 | double t3 = Math.sin(a1)*Math.sin(b1);
104 | double tt = Math.acos(t1 + t2 + t3);
105 |
106 | return 6366000*tt;
107 | }
108 |
109 | private boolean shouldCancel() {
110 | boolean cancel = false;
111 |
112 | if ((trackMinPoints > 0) && (inSegment < trackMinPoints)) {
113 | Debug.log(String.format(Locale.ENGLISH, "CANCEL POINTS %d < %d", inSegment, trackMinPoints));
114 | cancel = true;
115 | }
116 |
117 | if ((trackMinTime > 0) && (lastTime - lastTrackStartTime < trackMinTime * 1000)) {
118 | Debug.log(String.format(Locale.ENGLISH, "CANCEL TIME %d < %d", (int)((lastTime - lastTrackStartTime) / 1000), trackMinTime));
119 | cancel = true;
120 | }
121 |
122 | if (trackMinDistance > 0) {
123 | double m = gps2m(trackLatMin, trackLongMin, trackLatMax, trackLongMax);
124 | if (m < trackMinDistance) {
125 | Debug.log(String.format(Locale.ENGLISH, "CANCEL DISTANCE %d < %d", (int)m, trackMinDistance));
126 | cancel = true;
127 | }
128 | }
129 |
130 | return cancel;
131 | }
132 |
133 | private String performExport(OnExportProgressListener callback, Cursor cursor) {
134 | String filename = "";
135 | switch (format) {
136 | case GPX: filename = Application.SDCARD_PATH + "/geolog.gpx"; break;
137 | case KML: filename = Application.SDCARD_PATH + "/geolog.kml"; break;
138 | }
139 | (new File(filename)).delete();
140 |
141 | try {
142 | FileOutputStream fos = new FileOutputStream(filename, false);
143 | try {
144 | FormatWriter writer = null;
145 | switch (format) {
146 | case GPX: writer = new GPXWriter(fos); break;
147 | case KML: writer = new KMLWriter(fos); break;
148 | }
149 |
150 | String exporter = "GeoLog";
151 |
152 | PackageManager pm = context.getPackageManager();
153 | if (pm != null) {
154 | try {
155 | PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
156 | if (pi != null) {
157 | exporter += " v" + pi.versionName;
158 | }
159 | } catch (Exception e) {
160 | }
161 | }
162 |
163 | // only used for debug logging - exported is in writer
164 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
165 | simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
166 |
167 | writer.header(exporter);
168 |
169 | lastTrackStartBytes = fos.getChannel().position();
170 | writer.startSegment();
171 |
172 | Cursor c = cursor;
173 | if ((c != null) && (c.getCount() > 0)) {
174 | Database.Location loc = new Database.Location();
175 |
176 | c.moveToFirst();
177 | int index = 0;
178 | int count = c.getCount();
179 | while (true) {
180 | loc.loadFromCursor(c);
181 |
182 | Debug.log(String.format(Locale.ENGLISH, "WRITE %d %.5f %.5f %s", index, loc.getLatitude(), loc.getLongitude(), simpleDateFormat.format(new Date(loc.getTime()))));
183 |
184 | if (lastTrackStartTime == 0) {
185 | lastTrackStartTime = loc.getTime();
186 | trackLatMin = loc.getLatitude();
187 | trackLatMax = loc.getLatitude();
188 | trackLongMin = loc.getLongitude();
189 | trackLongMax = loc.getLongitude();
190 | }
191 | isSegmentStart = isSegmentStart || loc.isSegmentStart(); // carries over in case not used
192 |
193 | boolean ok =
194 | (loc.getAccuracySetting() == Accuracy.NONE) ||
195 | ((loc.getAccuracySetting() == Accuracy.LOW) && (
196 | ((loc.getActivity() == Database.Activity.UNKNOWN) && ((accuracyLowPowerUnknown <= 0) || loc.getAccuracyDistance() <= accuracyLowPowerUnknown)) ||
197 | ((loc.getActivity() == Database.Activity.STILL) && ((accuracyLowPowerStill <= 0) || loc.getAccuracyDistance() <= accuracyLowPowerStill)) ||
198 | ((loc.getActivity() == Database.Activity.FOOT) && ((accuracyLowPowerFoot <= 0) || loc.getAccuracyDistance() <= accuracyLowPowerFoot)) ||
199 | ((loc.getActivity() == Database.Activity.BICYCLE) && ((accuracyLowPowerBicycle <= 0) || loc.getAccuracyDistance() <= accuracyLowPowerBicycle)) ||
200 | ((loc.getActivity() == Database.Activity.VEHICLE) && ((accuracyLowPowerVehicle <= 0) || loc.getAccuracyDistance() <= accuracyLowPowerVehicle))
201 | )) ||
202 | ((loc.getAccuracySetting() == Accuracy.HIGH) && (
203 | ((loc.getActivity() == Database.Activity.UNKNOWN) && ((accuracyHighAccuracyUnknown <= 0) || loc.getAccuracyDistance() <= accuracyHighAccuracyUnknown)) ||
204 | ((loc.getActivity() == Database.Activity.STILL) && ((accuracyHighAccuracyStill <= 0) || loc.getAccuracyDistance() <= accuracyHighAccuracyStill)) ||
205 | ((loc.getActivity() == Database.Activity.FOOT) && ((accuracyHighAccuracyFoot <= 0) || loc.getAccuracyDistance() <= accuracyHighAccuracyFoot)) ||
206 | ((loc.getActivity() == Database.Activity.BICYCLE) && ((accuracyHighAccuracyBicycle <= 0) || loc.getAccuracyDistance() <= accuracyHighAccuracyBicycle)) ||
207 | ((loc.getActivity() == Database.Activity.VEHICLE) && ((accuracyHighAccuracyVehicle <= 0) || loc.getAccuracyDistance() <= accuracyHighAccuracyVehicle))
208 | ));
209 |
210 | if (ok) {
211 | if (isSegmentStart) {
212 | if (loc.getTime() - lastTime < trackMergeGap * 1000) {
213 | Debug.log(String.format(Locale.ENGLISH, "MERGE %d %ds", index, (int)((loc.getTime() - lastTime) / 1000)));
214 |
215 | isSegmentStart = false;
216 | }
217 | }
218 |
219 | if (isSegmentStart) {
220 | if (inSegment > 0) {
221 | if (shouldCancel()) {
222 | fos.getChannel().position(lastTrackStartBytes);
223 | fos.getChannel().truncate(lastTrackStartBytes);
224 | } else {
225 | writer.endSegment();
226 | }
227 | Debug.log(String.format(Locale.ENGLISH, "TRACK %d", index));
228 | lastTrackStartBytes = fos.getChannel().position();
229 | writer.startSegment();
230 |
231 | lastTrackStartTime = loc.getTime();
232 | trackLatMin = loc.getLatitude();
233 | trackLatMax = loc.getLatitude();
234 | trackLongMin = loc.getLongitude();
235 | trackLongMax = loc.getLongitude();
236 |
237 | inSegment = 0;
238 | }
239 | isSegmentStart = false;
240 | }
241 |
242 | // if !ok we don't know location is correct, so we only do this here
243 | trackLatMin = Math.min(trackLatMin, loc.getLatitude());
244 | trackLatMax = Math.max(trackLatMax, loc.getLatitude());
245 | trackLongMin = Math.min(trackLongMin, loc.getLongitude());
246 | trackLongMax = Math.max(trackLongMax, loc.getLongitude());
247 |
248 | writer.point(loc);
249 | inSegment++;
250 | } else {
251 | Debug.log(String.format(Locale.ENGLISH, "SKIP %d %dm", index, (int)loc.getAccuracyDistance()));
252 | }
253 |
254 | lastTime = loc.getTime(); // take into account even if we don't store point, because we know time is correct
255 |
256 | index++;
257 | if (callback != null) callback.OnExportProgress(index, count);
258 | if (!c.moveToNext()) break;
259 | }
260 | }
261 |
262 | if ((inSegment == 0) || shouldCancel()) {
263 | fos.getChannel().position(lastTrackStartBytes);
264 | fos.getChannel().truncate(lastTrackStartBytes);
265 | } else {
266 | writer.endSegment();
267 | }
268 |
269 | writer.footer();
270 | } finally {
271 | fos.close();
272 | }
273 | } catch (Exception e) {
274 | e.printStackTrace();
275 | (new File(filename)).delete();
276 | return null;
277 | }
278 | return filename;
279 | }
280 |
281 | private class ExportAsync extends AsyncTask {
282 | private ProgressDialog dialog = null;
283 |
284 | @Override
285 | protected void onPreExecute() {
286 | dialog = new ProgressDialog(context);
287 | dialog.setTitle(R.string.export_exporting);
288 | dialog.setIndeterminate(false);
289 | dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
290 | dialog.setCancelable(false);
291 | dialog.setProgress(0);
292 | dialog.setMax(1);
293 | dialog.show();
294 | }
295 |
296 | @Override
297 | protected void onProgressUpdate(Integer... values) {
298 | dialog.setMax(values[1]);
299 | dialog.setProgress(values[0]);
300 | }
301 |
302 | @Override
303 | protected String doInBackground(Cursor... params) {
304 | return performExport(new OnExportProgressListener() {
305 | @Override
306 | public void OnExportProgress(int cur, int total) {
307 | publishProgress(cur, total);
308 | }
309 | }, params[0]);
310 | }
311 |
312 | @Override
313 | protected void onPostExecute(String result) {
314 | dialog.dismiss();
315 |
316 | (new AlertDialog.Builder(context)).
317 | setTitle(R.string.export_export).
318 | setMessage(Html.fromHtml(context.getString((result == null) ? R.string.export_failed : R.string.export_complete))).
319 | setPositiveButton(R.string.generic_ok, null).
320 | show();
321 | }
322 | }
323 |
324 | public Format getFormat() {
325 | return format;
326 | }
327 |
328 | public void setFormat(Format format) {
329 | this.format = format;
330 | }
331 |
332 | public long getTrackMergeGap() {
333 | return trackMergeGap;
334 | }
335 |
336 | public void setTrackMergeGap(long trackMergeGap) {
337 | this.trackMergeGap = trackMergeGap;
338 | }
339 |
340 | public long getDateStart() {
341 | return dateStart;
342 | }
343 |
344 | public void setDateStart(long dateStart) {
345 | this.dateStart = dateStart;
346 | }
347 |
348 | public long getDateEnd() {
349 | return dateEnd;
350 | }
351 |
352 | public void setDateEnd(long dateEnd) {
353 | this.dateEnd = dateEnd;
354 | }
355 |
356 | public long getAccuracyLowPowerUnknown() {
357 | return accuracyLowPowerUnknown;
358 | }
359 |
360 | public void setAccuracyLowPowerUnknown(long accuracyLowPowerUnknown) {
361 | this.accuracyLowPowerUnknown = accuracyLowPowerUnknown;
362 | }
363 |
364 | public long getAccuracyLowPowerStill() {
365 | return accuracyLowPowerStill;
366 | }
367 |
368 | public void setAccuracyLowPowerStill(long accuracyLowPowerStill) {
369 | this.accuracyLowPowerStill = accuracyLowPowerStill;
370 | }
371 |
372 | public long getAccuracyLowPowerFoot() {
373 | return accuracyLowPowerFoot;
374 | }
375 |
376 | public void setAccuracyLowPowerFoot(long accuracyLowPowerFoot) {
377 | this.accuracyLowPowerFoot = accuracyLowPowerFoot;
378 | }
379 |
380 | public long getAccuracyLowPowerBicycle() {
381 | return accuracyLowPowerBicycle;
382 | }
383 |
384 | public void setAccuracyLowPowerBicycle(long accuracyLowPowerBicycle) {
385 | this.accuracyLowPowerBicycle = accuracyLowPowerBicycle;
386 | }
387 |
388 | public long getAccuracyLowPowerVehicle() {
389 | return accuracyLowPowerVehicle;
390 | }
391 |
392 | public void setAccuracyLowPowerVehicle(long accuracyLowPowerVehicle) {
393 | this.accuracyLowPowerVehicle = accuracyLowPowerVehicle;
394 | }
395 |
396 | public long getAccuracyHighAccuracyUnknown() {
397 | return accuracyHighAccuracyUnknown;
398 | }
399 |
400 | public void setAccuracyHighAccuracyUnknown(long accuracyHighAccuracyUnknown) {
401 | this.accuracyHighAccuracyUnknown = accuracyHighAccuracyUnknown;
402 | }
403 |
404 | public long getAccuracyHighAccuracyStill() {
405 | return accuracyHighAccuracyStill;
406 | }
407 |
408 | public void setAccuracyHighAccuracyStill(long accuracyHighAccuracyStill) {
409 | this.accuracyHighAccuracyStill = accuracyHighAccuracyStill;
410 | }
411 |
412 | public long getAccuracyHighAccuracyFoot() {
413 | return accuracyHighAccuracyFoot;
414 | }
415 |
416 | public void setAccuracyHighAccuracyFoot(long accuracyHighAccuracyFoot) {
417 | this.accuracyHighAccuracyFoot = accuracyHighAccuracyFoot;
418 | }
419 |
420 | public long getAccuracyHighAccuracyBicycle() {
421 | return accuracyHighAccuracyBicycle;
422 | }
423 |
424 | public void setAccuracyHighAccuracyBicycle(long accuracyHighAccuracyBicycle) {
425 | this.accuracyHighAccuracyBicycle = accuracyHighAccuracyBicycle;
426 | }
427 |
428 | public long getAccuracyHighAccuracyVehicle() {
429 | return accuracyHighAccuracyVehicle;
430 | }
431 |
432 | public void setAccuracyHighAccuracyVehicle(long accuracyHighAccuracyVehicle) {
433 | this.accuracyHighAccuracyVehicle = accuracyHighAccuracyVehicle;
434 | }
435 |
436 | public long getTrackMinPoints() {
437 | return trackMinPoints;
438 | }
439 |
440 | public void setTrackMinPoints(long trackMinPoints) {
441 | this.trackMinPoints = trackMinPoints;
442 | }
443 |
444 | public long getTrackMinTime() {
445 | return trackMinTime;
446 | }
447 |
448 | public void setTrackMinTime(long trackMinTime) {
449 | this.trackMinTime = trackMinTime;
450 | }
451 |
452 | public long getTrackMinDistance() {
453 | return trackMinDistance;
454 | }
455 |
456 | public void setTrackMinDistance(long trackMinDistance) {
457 | this.trackMinDistance = trackMinDistance;
458 | }
459 |
460 | private abstract class FormatWriter {
461 | protected OutputStream os = null;
462 |
463 | public FormatWriter(OutputStream os) {
464 | this.os = os;
465 | }
466 |
467 | protected void write(String string) throws UnsupportedEncodingException, IOException {
468 | os.write(string.getBytes("US-ASCII"));
469 | }
470 |
471 | public abstract void header(String exporter) throws UnsupportedEncodingException, IOException;
472 | public abstract void startSegment() throws UnsupportedEncodingException, IOException;
473 | public abstract void point(Database.Location loc) throws UnsupportedEncodingException, IOException;
474 | public abstract void endSegment() throws UnsupportedEncodingException, IOException;
475 | public abstract void footer() throws UnsupportedEncodingException, IOException;
476 | }
477 |
478 | private class GPXWriter extends FormatWriter {
479 | protected SimpleDateFormat simpleDateFormat = null;
480 |
481 | public GPXWriter(OutputStream os) {
482 | super(os);
483 | simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
484 | simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
485 | }
486 |
487 | @Override
488 | public void header(String exporter) throws UnsupportedEncodingException, IOException {
489 | write(
490 | "\r\n" +
491 | "\r\n" +
492 | " \r\n"
493 | );
494 | }
495 |
496 | @Override
497 | public void startSegment() throws UnsupportedEncodingException, IOException {
498 | write(
499 | " \r\n"
500 | );
501 | }
502 |
503 | @Override
504 | public void point(Location loc) throws UnsupportedEncodingException, IOException {
505 | write(String.format(Locale.ENGLISH, " \r\n", loc.getLatitude(), loc.getLongitude(), simpleDateFormat.format(new Date(loc.getTime()))));
506 | }
507 |
508 | @Override
509 | public void endSegment() throws UnsupportedEncodingException, IOException {
510 | write(
511 | " \r\n"
512 | );
513 | }
514 |
515 | @Override
516 | public void footer() throws UnsupportedEncodingException, IOException {
517 | write(
518 | " \r\n" +
519 | "\r\n"
520 | );
521 | }
522 | }
523 |
524 | private class KMLWriter extends FormatWriter {
525 | protected int trackIndex = 1;
526 | protected String last = "";
527 |
528 | public KMLWriter(OutputStream os) {
529 | super(os);
530 | }
531 |
532 | @Override
533 | public void header(String exporter) throws UnsupportedEncodingException, IOException {
534 | write(
535 | "\r\n" +
536 | "\r\n" +
538 | " \r\n" +
539 | " Exported by " + exporter + "\r\n" +
540 | " \r\n" +
541 | " 1\r\n" +
542 | " 1\r\n" +
543 | " \r\n" +
544 | " \r\n" +
545 | " Tracks\r\n" +
546 | " \r\n" +
547 | " 1\r\n" +
548 | " 1\r\n"
549 | );
550 | }
551 |
552 | @Override
553 | public void startSegment() throws UnsupportedEncodingException, IOException {
554 | write(
555 | " \r\n" +
556 | " 1\r\n" +
557 | " 1\r\n" +
558 | " #red\r\n" +
559 | " Track " + String.valueOf(trackIndex) + "\r\n" +
560 | " \r\n" +
561 | " \r\n" +
562 | " true\r\n" +
563 | " true\r\n" +
564 | " clampToGround\r\n" +
565 | " \r\n"
566 | );
567 | trackIndex++;
568 | }
569 |
570 | @Override
571 | public void point(Location loc) throws UnsupportedEncodingException, IOException {
572 | String now = String.format(Locale.ENGLISH, " %.5f,%.5f\r\n", loc.getLongitude(), loc.getLatitude());
573 | if (!now.equals(last)) {
574 | write(now);
575 | last = now;
576 | }
577 | }
578 |
579 | @Override
580 | public void endSegment() throws UnsupportedEncodingException, IOException {
581 | write(
582 | " \r\n" +
583 | " \r\n" +
584 | " \r\n"
585 | );
586 | }
587 |
588 | @Override
589 | public void footer() throws UnsupportedEncodingException, IOException {
590 | write(
591 | " \r\n" +
592 | " \r\n" +
593 | "\r\n"
594 | );
595 | }
596 | }
597 | }
598 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/service/BackgroundService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.service;
18 |
19 | import java.util.Locale;
20 |
21 | import com.google.android.gms.common.ConnectionResult;
22 | import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
23 | import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
24 | import com.google.android.gms.location.ActivityRecognitionClient;
25 | import com.google.android.gms.location.ActivityRecognitionResult;
26 | import com.google.android.gms.location.DetectedActivity;
27 | import com.google.android.gms.location.LocationClient;
28 | import com.google.android.gms.location.LocationListener;
29 | import com.google.android.gms.location.LocationRequest;
30 |
31 | import eu.chainfire.geolog.Debug;
32 | import eu.chainfire.geolog.R;
33 | import eu.chainfire.geolog.data.Database;
34 | import eu.chainfire.geolog.data.Database.Accuracy;
35 | import eu.chainfire.geolog.data.Database.Activity;
36 | import eu.chainfire.geolog.data.Database.Profile.Type;
37 | import eu.chainfire.geolog.ui.MainActivity;
38 | import eu.chainfire.geolog.ui.SettingsFragment;
39 |
40 | import android.annotation.SuppressLint;
41 | import android.app.AlarmManager;
42 | import android.app.Notification;
43 | import android.app.NotificationManager;
44 | import android.app.PendingIntent;
45 | import android.app.Service;
46 | import android.content.BroadcastReceiver;
47 | import android.content.Context;
48 | import android.content.Intent;
49 | import android.content.IntentFilter;
50 | import android.content.SharedPreferences;
51 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
52 | import android.location.Location;
53 | import android.os.BatteryManager;
54 | import android.os.Build;
55 | import android.os.Bundle;
56 | import android.os.Handler;
57 | import android.os.IBinder;
58 | import android.os.Looper;
59 | import android.os.PowerManager;
60 | import android.os.SystemClock;
61 | import android.preference.PreferenceManager;
62 | import android.support.v4.content.LocalBroadcastManager;
63 |
64 | public class BackgroundService extends Service {
65 | public static void startService(Context context) {
66 | context.startService(new Intent(context.getApplicationContext(), BackgroundService.class));
67 | }
68 |
69 | private static String EXTRA_ALARM_CALLBACK = "eu.chainfire.geolog.EXTRA.ALARM_CALLBACK";
70 |
71 | private volatile ServiceThread thread = null;
72 | private volatile PowerManager.WakeLock wakelock = null;
73 |
74 | private volatile NotificationManager notificationManager;
75 | private volatile PendingIntent notificationIntent;
76 | private volatile Notification.Builder notificationBuilder;
77 |
78 | @SuppressLint("NewApi")
79 | @Override
80 | public void onCreate() {
81 | super.onCreate();
82 | Debug.log("Service created");
83 |
84 | if (thread == null) {
85 | Debug.log("Launching thread");
86 | thread = new ServiceThread();
87 | thread.setContext(getApplicationContext());
88 | thread.start();
89 | }
90 |
91 | PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
92 | wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GeoLog Wakelock");
93 |
94 | Intent i = new Intent();
95 | i.setAction(Intent.ACTION_MAIN);
96 | i.addCategory(Intent.CATEGORY_LAUNCHER);
97 | i.setClass(this, MainActivity.class);
98 | i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NEW_TASK);
99 | notificationIntent = PendingIntent.getActivity(this, 0, i, 0);
100 |
101 | notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
102 | notificationBuilder = (new Notification.Builder(this)).
103 | setSmallIcon(R.drawable.ic_stat_service).
104 | setContentIntent(notificationIntent).
105 | setWhen(System.currentTimeMillis()).
106 | setAutoCancel(false).
107 | setOngoing(true).
108 | setContentTitle(getString(R.string.service_title)).
109 | setContentText(getString(R.string.service_waiting));
110 |
111 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
112 | notificationBuilder.setShowWhen(false);
113 | }
114 |
115 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
116 | /* quick turn off, maybe ? if added, make sure to add a button to preferences to disable these buttons
117 | notificationBuilder.
118 | setPriority(Notification.PRIORITY_MAX).
119 | addAction(0, "A", notificationIntent).
120 | addAction(0, "B", notificationIntent).
121 | addAction(0, "C", notificationIntent);
122 | */
123 | }
124 |
125 | updateNotification();
126 | }
127 |
128 | @SuppressWarnings("deprecation")
129 | @SuppressLint("NewApi")
130 | private void updateNotification() {
131 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
132 | notificationManager.notify(1, notificationBuilder.build());
133 | } else {
134 | notificationManager.notify(1, notificationBuilder.getNotification());
135 | }
136 | }
137 |
138 | @Override
139 | public void onDestroy() {
140 | Debug.log("Stopping thread");
141 | thread.signalStop();
142 | try { thread.join(); } catch (Exception e) { }
143 | thread = null;
144 |
145 | ((NotificationManager)getSystemService(NOTIFICATION_SERVICE)).cancelAll();
146 |
147 | Debug.log("Service destroyed");
148 | super.onDestroy();
149 | }
150 |
151 | @Override
152 | public int onStartCommand(Intent intent, int flags, int startId) {
153 | thread.processIntent(intent);
154 | return START_STICKY;
155 | }
156 |
157 | @Override
158 | public IBinder onBind(Intent intent) {
159 | return null;
160 | }
161 |
162 | private class ServiceThread extends Thread {
163 | private static final int FLAG_SETUP = 1;
164 | private static final int FLAG_ACTIVITY_UPDATE = 2;
165 | private static final int FLAG_LOCATION_UPDATE = 4;
166 | private static final int FLAG_PROFILE = 8;
167 |
168 | private volatile Context context = null;
169 | private volatile Handler handler = null;
170 |
171 | private volatile AlarmManager alarm = null;
172 | private volatile PendingIntent alarmCallback = null;
173 |
174 | private volatile ActivityRecognitionClient activityClient = null;
175 | private volatile boolean activityConnected = false;
176 | private volatile PendingIntent activityIntent = null;
177 |
178 | private volatile LocationClient locationClient = null;
179 | private volatile boolean locationConnected = false;
180 |
181 | private volatile Database.Helper databaseHelper = null;
182 |
183 | private volatile boolean metric = true;
184 |
185 | private volatile Database.Activity lastActivity = Database.Activity.UNKNOWN;
186 | private volatile int lastConfidence = 0;
187 | private volatile Location lastLocLoc = null;
188 | private volatile Database.Location lastLocation = null;
189 | private volatile long lastLocationDuplicates = 0;
190 | private volatile long lastNonUnknown = 0;
191 |
192 | private volatile Database.Accuracy lastLocationAccuracy = Database.Accuracy.NONE;
193 | private volatile int lastLocationInterval = -1;
194 | private volatile int lastActivityInterval = -1;
195 | private volatile int lastBatteryLevel = 0;
196 | private volatile boolean isSegmentStart = true;
197 | private volatile long lastProfileUpdate = SystemClock.elapsedRealtime();
198 |
199 | private volatile long scheduledReduceAccuracyTime = 0;
200 |
201 | private volatile SharedPreferences prefs = null;
202 | private volatile Database.Profile currentProfile = null;
203 |
204 | // Main thread
205 |
206 | public void setContext(Context context) {
207 | this.context = context;
208 | }
209 |
210 | public void signalStop() {
211 | if (handler != null) {
212 | handler.post(new Runnable() {
213 | @Override
214 | public void run() {
215 | Looper.myLooper().quit();
216 | }
217 | });
218 | }
219 | }
220 |
221 | public boolean processIntent(Intent intent) {
222 | if (intent == null) return false;
223 |
224 | if (ActivityRecognitionResult.hasResult(intent)) {
225 | processActivity(intent);
226 | return true;
227 | } else if (intent.hasExtra(EXTRA_ALARM_CALLBACK)) {
228 | processAlarmCallback(intent);
229 | return true;
230 | }
231 |
232 | return false;
233 | }
234 |
235 | private void processActivity(Intent intent) {
236 | if (handler != null) {
237 | final DetectedActivity activity = ActivityRecognitionResult.extractResult(intent).getMostProbableActivity();
238 | if (Database.isDetectedActivityValid(activity)) {
239 | wakelock.acquire();
240 | handler.post(new Runnable() {
241 | @Override
242 | public void run() {
243 | setActivity(Database.activityFromDetectedActivity(activity), activity.getConfidence());
244 | wakelock.release();
245 | }
246 | });
247 | }
248 | }
249 | }
250 |
251 | private void processAlarmCallback(Intent intent) {
252 | if (handler != null) {
253 | wakelock.acquire();
254 | handler.post(new Runnable() {
255 | @Override
256 | public void run() {
257 | updateListeners(0);
258 | wakelock.release();
259 | }
260 | });
261 | }
262 | }
263 |
264 | // Service thread
265 |
266 | private void setActivity(Database.Activity activity, int confidence) {
267 | if ((activity == Activity.UNKNOWN) && (SystemClock.elapsedRealtime() < lastNonUnknown + (2 * 60 * 1000)) && (SystemClock.elapsedRealtime() > lastNonUnknown)) {
268 | return;
269 | }
270 | if (activity != Activity.UNKNOWN) {
271 | lastNonUnknown = SystemClock.elapsedRealtime();
272 | }
273 |
274 | Debug.log(String.format(Locale.ENGLISH, "A: %s (%d%%)", Database.activityToString(activity), confidence));
275 |
276 | lastActivity = activity;
277 | lastConfidence = confidence;
278 | updateListeners(FLAG_ACTIVITY_UPDATE);
279 | }
280 |
281 | private void setLocation(Location location) {
282 | Debug.log(String.format(Locale.ENGLISH, "L: lat=%.8f long=%.5f alt=%.5f bearing=%.4f speed=%.4f accuracy=%.2f", location.getLatitude(), location.getLongitude(), location.getAltitude(), location.getBearing(), location.getSpeed(), location.getAccuracy()));
283 |
284 | lastLocLoc = location;
285 | updateListeners(FLAG_LOCATION_UPDATE);
286 | }
287 |
288 | @SuppressLint("NewApi")
289 | private void updateListeners(int flags) {
290 | if (!(activityConnected && locationConnected && (currentProfile != null))) return;
291 |
292 | if (currentProfile.getType() == Type.OFF) {
293 | stopSelf();
294 | }
295 |
296 | if ((flags & FLAG_PROFILE) == FLAG_PROFILE) {
297 | Debug.log("Profile update");
298 |
299 | lastActivity = Activity.UNKNOWN;
300 | lastConfidence = 0;
301 |
302 | scheduledReduceAccuracyTime = 0L;
303 | lastProfileUpdate = SystemClock.elapsedRealtime();
304 | }
305 |
306 | Accuracy originalAccuracy = lastLocationAccuracy;
307 |
308 | Database.Profile.ActivitySettings wanted = currentProfile.getActivitySettings(lastActivity);
309 | Database.Accuracy wantedAccuracy = wanted.getAccuracy();
310 | int wantedLocationInterval = wanted.getLocationInterval();
311 | int wantedActivityInterval = wanted.getActivityInterval();
312 |
313 | boolean allowUpdateActivityInterval = true;
314 | boolean allowUpdateLocationInterval = true;
315 | boolean allowUpdateLocationAccuracy = true;
316 |
317 | if (
318 | (
319 | (SystemClock.elapsedRealtime() > lastProfileUpdate + (90 * 1000)) ||
320 | (SystemClock.elapsedRealtime() < lastProfileUpdate)
321 | ) &&
322 | (currentProfile.getReduceAccuracyDelay() > 0) &&
323 | (
324 | (wantedActivityInterval > lastActivityInterval) ||
325 | (wantedLocationInterval > lastLocationInterval) ||
326 | (Database.accuracyToInt(wantedAccuracy) < Database.accuracyToInt(lastLocationAccuracy))
327 | )
328 | ) {
329 | long left = 0;
330 |
331 | if (scheduledReduceAccuracyTime == 0) {
332 | left = currentProfile.getReduceAccuracyDelay() * 1000;
333 | scheduledReduceAccuracyTime = SystemClock.elapsedRealtime() + left;
334 | alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, scheduledReduceAccuracyTime + 1000, alarmCallback);
335 | } else {
336 | left = scheduledReduceAccuracyTime - SystemClock.elapsedRealtime();
337 | }
338 |
339 | if (left <= 0) scheduledReduceAccuracyTime = 0L;
340 |
341 | allowUpdateActivityInterval = ((left <= 0) || (wantedActivityInterval < lastActivityInterval) || (lastActivityInterval == -1));
342 | allowUpdateLocationInterval = ((left <= 0) || (wantedLocationInterval < lastLocationInterval) || (lastLocationInterval == -1));
343 | allowUpdateLocationAccuracy = ((left <= 0) || (Database.accuracyToInt(wantedAccuracy) > Database.accuracyToInt(lastLocationAccuracy)));
344 |
345 | if (!allowUpdateActivityInterval) wantedActivityInterval = lastActivityInterval;
346 | if (!allowUpdateLocationInterval) wantedLocationInterval = lastLocationInterval;
347 | if (!allowUpdateLocationAccuracy) wantedAccuracy = lastLocationAccuracy;
348 |
349 | if (!allowUpdateActivityInterval) Debug.log(String.format(Locale.ENGLISH, "ActivityInterval --> Delay (%ds remaining)", (left / 1000)));
350 | if (!allowUpdateLocationInterval) Debug.log(String.format(Locale.ENGLISH, "LocationInterval --> Delay (%ds remaining)", (left / 1000)));
351 | if (!allowUpdateLocationAccuracy) Debug.log(String.format(Locale.ENGLISH, "LocationAccuracy --> Delay (%ds remaining)", (left / 1000)));
352 | } else {
353 | scheduledReduceAccuracyTime = 0;
354 | alarm.cancel(alarmCallback);
355 | }
356 |
357 | if ((wantedAccuracy != lastLocationAccuracy) || (wantedLocationInterval != lastLocationInterval)) {
358 | String s = "NONE";
359 | if (wantedAccuracy == Accuracy.LOW) s = "LOW";
360 | if (wantedAccuracy == Accuracy.HIGH) s = "HIGH";
361 | Debug.log(String.format(Locale.ENGLISH, "Location --> %s %ds", s, wantedLocationInterval));
362 |
363 | locationClient.removeLocationUpdates(locationListener);
364 |
365 | if ((wantedAccuracy != Accuracy.NONE) && (wantedLocationInterval > 0)) {
366 | if ((lastLocationAccuracy == Accuracy.NONE) || (lastLocationInterval == 0)) isSegmentStart = true;
367 |
368 | LocationRequest req = new LocationRequest();
369 | req.setFastestInterval(wantedLocationInterval * 250);
370 | req.setInterval(wantedLocationInterval * 1000);
371 | if (lastLocationAccuracy == Accuracy.NONE) req.setPriority(LocationRequest.PRIORITY_NO_POWER);
372 | if (lastLocationAccuracy == Accuracy.LOW) req.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
373 | if (lastLocationAccuracy == Accuracy.HIGH) req.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
374 | locationClient.requestLocationUpdates(req, locationListener);
375 | }
376 |
377 | lastLocationAccuracy = wantedAccuracy;
378 | lastLocationInterval = wantedLocationInterval;
379 | }
380 |
381 | if (wantedActivityInterval != lastActivityInterval) {
382 | Debug.log(String.format(Locale.ENGLISH, "Activity --> %ds", wantedActivityInterval));
383 |
384 | activityClient.removeActivityUpdates(activityIntent);
385 |
386 | if ((wantedActivityInterval == 0) && (lastActivityInterval != 0)) {
387 | lastActivity = Activity.UNKNOWN;
388 | lastConfidence = 0;
389 | }
390 |
391 | if (wantedActivityInterval != 0) {
392 | activityClient.requestActivityUpdates(wantedActivityInterval * 1000, activityIntent);
393 | }
394 |
395 | lastActivityInterval = wantedActivityInterval;
396 | }
397 |
398 | if ((flags & FLAG_ACTIVITY_UPDATE) == FLAG_ACTIVITY_UPDATE) {
399 | if (
400 | (lastLocation == null) ||
401 | (lastLocation.getActivity() != lastActivity) ||
402 | (lastLocation.getConfidence() != lastConfidence)
403 | ) {
404 | Debug.log("Activity update");
405 | if (lastLocation == null) {
406 | lastLocationDuplicates = 0;
407 |
408 | lastLocation = new Database.Location();
409 | lastLocation.setTime(System.currentTimeMillis());
410 | }
411 | lastLocation.setActivity(lastActivity);
412 | lastLocation.setConfidence(lastConfidence);
413 | }
414 | } else if ((flags & FLAG_LOCATION_UPDATE) == FLAG_LOCATION_UPDATE) {
415 | if (
416 | (lastLocation == null) ||
417 | (lastLocLoc == null) ||
418 | (lastLocation.getLatitude() != lastLocLoc.getLatitude()) ||
419 | (lastLocation.getLongitude() != lastLocLoc.getLongitude()) ||
420 | (lastLocation.getAccuracyDistance() > lastLocLoc.getAccuracy()) ||
421 | (lastLocation.getActivity() != lastActivity) ||
422 | (lastLocation.getConfidence() != lastConfidence) ||
423 | (lastLocation.getAccuracySetting() != originalAccuracy) ||
424 | (isSegmentStart)
425 | ) {
426 | Debug.log("Location update");
427 |
428 | if (lastLocationDuplicates > 0) {
429 | Debug.log("Saving last duplicate (out of " + String.valueOf(lastLocationDuplicates) + ")");
430 | Database.Location.copy(databaseHelper, lastLocation);
431 | }
432 |
433 | lastLocationDuplicates = 0;
434 |
435 | Database.Location loc = new Database.Location();
436 | loc.setActivity(lastActivity);
437 | loc.setConfidence(lastConfidence);
438 | loc.setBattery(lastBatteryLevel);
439 | loc.setAccuracySetting(originalAccuracy);
440 | loc.isSegmentStart(isSegmentStart);
441 | loc.loadFromLocation(lastLocLoc);
442 | Debug.log("Saved to database: " + String.valueOf(loc.saveToDatabase(databaseHelper)));
443 |
444 | lastLocation = loc;
445 | isSegmentStart = false;
446 | } else if (
447 | (lastLocation != null) &&
448 | (lastLocLoc != null)
449 | ) {
450 | lastLocationDuplicates++;
451 |
452 | lastLocation.setActivity(lastActivity);
453 | lastLocation.setConfidence(lastConfidence);
454 | lastLocation.setBattery(lastBatteryLevel);
455 | lastLocation.setAccuracySetting(originalAccuracy);
456 | lastLocation.isSegmentStart(isSegmentStart);
457 | lastLocation.loadFromLocation(lastLocLoc);
458 | }
459 | }
460 |
461 | if (lastLocation != null) {
462 | notificationBuilder.
463 | setWhen(lastLocation.getTime()).
464 | setContentText(String.format(Locale.ENGLISH, "%s ~ %d%% / %.5f, %.5f ~ %.0f%s", Database.activityToString(lastLocation.getActivity()), lastLocation.getConfidence(), lastLocation.getLatitude(), lastLocation.getLongitude(), metric ? lastLocation.getAccuracyDistance() : lastLocation.getAccuracyDistance() * SettingsFragment.METER_FEET_RATIO, metric ? "m" : "ft"));
465 |
466 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
467 | notificationBuilder.setShowWhen(true);
468 | }
469 |
470 | updateNotification();
471 | }
472 | }
473 |
474 | private ConnectionCallbacks activityConnectionCallbacks = new ConnectionCallbacks() {
475 | @Override
476 | public void onConnected(Bundle arg0) {
477 | Debug.log("ActivityRecognitionClient connected");
478 |
479 | activityConnected = true;
480 |
481 | updateListeners(FLAG_SETUP);
482 | }
483 |
484 | @Override
485 | public void onDisconnected() {
486 | Debug.log("ActivityRecognitionClient disconnected");
487 |
488 | activityConnected = false;
489 | }
490 | };
491 |
492 | private OnConnectionFailedListener activityConnectionFailed = new OnConnectionFailedListener() {
493 | @Override
494 | public void onConnectionFailed(ConnectionResult arg0) {
495 | Debug.log("ActivityRecognitionClient connection failed");
496 |
497 | activityConnected = false;
498 | signalStop();
499 | }
500 | };
501 |
502 | private LocationListener locationListener = new LocationListener() {
503 | @Override
504 | public void onLocationChanged(Location arg0) {
505 | wakelock.acquire();
506 | try {
507 | setLocation(arg0);
508 | } finally {
509 | wakelock.release();
510 | }
511 | }
512 | };
513 |
514 | private ConnectionCallbacks locationConnectionCallbacks = new ConnectionCallbacks() {
515 | @Override
516 | public void onConnected(Bundle arg0) {
517 | Debug.log("LocationClient connected");
518 |
519 | locationConnected = true;
520 |
521 | updateListeners(FLAG_SETUP);
522 | }
523 |
524 | @Override
525 | public void onDisconnected() {
526 | Debug.log("LocationClient disconnected");
527 |
528 | locationConnected = false;
529 | }
530 | };
531 |
532 | private OnConnectionFailedListener locationConnectionFailed = new OnConnectionFailedListener() {
533 | @Override
534 | public void onConnectionFailed(ConnectionResult arg0) {
535 | Debug.log("LocationClient connection failed");
536 |
537 | locationConnected = false;
538 | signalStop();
539 | }
540 | };
541 |
542 | private BroadcastReceiver databaseUpdated = new BroadcastReceiver() {
543 | @Override
544 | public void onReceive(Context context, Intent intent) {
545 | if (
546 | (currentProfile != null) &&
547 | (intent != null) &&
548 | intent.hasExtra(Database.Helper.EXTRA_TABLE) &&
549 | intent.getStringExtra(Database.Helper.EXTRA_TABLE).equals(Database.Profile.TABLE_NAME) &&
550 | intent.hasExtra(Database.Helper.EXTRA_ID) &&
551 | (intent.getLongExtra(Database.Helper.EXTRA_ID, 0) == currentProfile.getId())
552 | ) {
553 | currentProfile = Database.Profile.getById(databaseHelper, intent.getLongExtra(Database.Helper.EXTRA_ID, 0), currentProfile);
554 | updateListeners(FLAG_PROFILE);
555 | }
556 | }
557 | };
558 |
559 | private OnSharedPreferenceChangeListener preferencesUpdated = new OnSharedPreferenceChangeListener() {
560 | @Override
561 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
562 | if (key.equals(SettingsFragment.PREF_UNITS)) {
563 | metric = !prefs.getString(SettingsFragment.PREF_UNITS, SettingsFragment.PREF_UNITS_DEFAULT).equals(SettingsFragment.VALUE_UNITS_IMPERIAL);
564 | updateListeners(0);
565 | }
566 | if (key.equals(SettingsFragment.PREF_CURRENT_PROFILE)) {
567 | currentProfile = Database.Profile.getById(databaseHelper, sharedPreferences.getLong(key, 0), currentProfile);
568 | updateListeners(FLAG_PROFILE);
569 | }
570 | }
571 | };
572 |
573 | private BroadcastReceiver batteryReceiver = new BroadcastReceiver() {
574 | @Override
575 | public void onReceive(Context context, Intent intent) {
576 | int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
577 | boolean plugged = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
578 |
579 | boolean charging = (
580 | (status == BatteryManager.BATTERY_STATUS_CHARGING) ||
581 | ((status == BatteryManager.BATTERY_STATUS_FULL) && plugged)
582 | );
583 |
584 | int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
585 | if (charging) level += 100;
586 |
587 | lastBatteryLevel = level;
588 | }
589 | };
590 |
591 | @Override
592 | public void run() {
593 | Debug.log("Thread init");
594 |
595 | databaseHelper = Database.Helper.getInstance(context);
596 |
597 | alarm = (AlarmManager)context.getSystemService(ALARM_SERVICE);
598 | {
599 | Intent i = new Intent(context.getApplicationContext(), BackgroundService.class);
600 | i.putExtra(EXTRA_ALARM_CALLBACK, 1);
601 | alarmCallback = PendingIntent.getService(BackgroundService.this, 0, i, 0);
602 | }
603 |
604 | Looper.prepare();
605 | handler = new Handler();
606 |
607 | Debug.log("Registering for updates");
608 | prefs = PreferenceManager.getDefaultSharedPreferences(context);
609 | long id = prefs.getLong(SettingsFragment.PREF_CURRENT_PROFILE, 0);
610 | if (id > 0) currentProfile = Database.Profile.getById(databaseHelper, id, null);
611 | if (currentProfile == null) currentProfile = Database.Profile.getOffProfile(databaseHelper);
612 | metric = !prefs.getString(SettingsFragment.PREF_UNITS, SettingsFragment.PREF_UNITS_DEFAULT).equals(SettingsFragment.VALUE_UNITS_IMPERIAL);
613 | prefs.registerOnSharedPreferenceChangeListener(preferencesUpdated);
614 | LocalBroadcastManager.getInstance(context).registerReceiver(databaseUpdated, new IntentFilter(Database.Helper.NOTIFY_BROADCAST));
615 |
616 | Debug.log("Registering for power levels");
617 | context.registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
618 |
619 | Debug.log("Connecting ActivityRecognitionClient");
620 | activityIntent = PendingIntent.getService(context, 1, new Intent(context, BackgroundService.class), 0);
621 | activityClient = new ActivityRecognitionClient(context, activityConnectionCallbacks, activityConnectionFailed);
622 | activityClient.connect();
623 |
624 | Debug.log("Connecting LocationClient");
625 | locationClient = new LocationClient(context, locationConnectionCallbacks, locationConnectionFailed);
626 | locationClient.connect();
627 |
628 | Debug.log("Entering loop");
629 | handler.post(new Runnable() {
630 | @Override
631 | public void run() {
632 | updateListeners(FLAG_SETUP);
633 | }
634 | });
635 | Looper.loop();
636 | Debug.log("Exiting loop");
637 |
638 | context.unregisterReceiver(batteryReceiver);
639 |
640 | LocalBroadcastManager.getInstance(context).unregisterReceiver(databaseUpdated);
641 | prefs.unregisterOnSharedPreferenceChangeListener(preferencesUpdated);
642 |
643 | if (activityConnected) {
644 | activityClient.removeActivityUpdates(activityIntent);
645 | activityClient.disconnect();
646 | }
647 | if (locationConnected) {
648 | locationClient.removeLocationUpdates(locationListener);
649 | locationClient.disconnect();
650 | }
651 | }
652 | }
653 | }
654 |
--------------------------------------------------------------------------------
/GeoLog/src/eu/chainfire/geolog/ui/ExportActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 Jorrit "Chainfire" Jongma
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package eu.chainfire.geolog.ui;
18 |
19 | import java.sql.Date;
20 | import java.util.ArrayList;
21 | import java.util.Calendar;
22 | import java.util.Locale;
23 |
24 | import eu.chainfire.geolog.R;
25 | import eu.chainfire.geolog.data.Database;
26 | import eu.chainfire.geolog.data.Exporter;
27 | import eu.chainfire.geolog.data.Exporter.Format;
28 |
29 | import android.app.Activity;
30 | import android.app.AlertDialog;
31 | import android.content.Context;
32 | import android.content.DialogInterface;
33 | import android.content.DialogInterface.OnClickListener;
34 | import android.content.Intent;
35 | import android.content.SharedPreferences;
36 | import android.content.res.TypedArray;
37 | import android.database.Cursor;
38 | import android.os.AsyncTask;
39 | import android.os.Bundle;
40 | import android.preference.EditTextPreference;
41 | import android.preference.ListPreference;
42 | import android.preference.Preference;
43 | import android.preference.Preference.OnPreferenceClickListener;
44 | import android.preference.PreferenceActivity;
45 | import android.preference.PreferenceCategory;
46 | import android.preference.PreferenceManager;
47 | import android.preference.PreferenceScreen;
48 | import android.text.InputType;
49 | import android.text.format.DateFormat;
50 | import android.view.LayoutInflater;
51 | import android.view.Menu;
52 | import android.view.MenuItem;
53 | import android.view.MenuItem.OnMenuItemClickListener;
54 | import android.view.View;
55 | import android.widget.DatePicker;
56 | import android.widget.LinearLayout;
57 | import android.widget.TimePicker;
58 |
59 | @SuppressWarnings("deprecation")
60 | public class ExportActivity extends PreferenceActivity {
61 | public static final String EXTRA_LOG_ID = "eu.chainfire.geolog.ProfileActivity.EXTRA.LOG_ID";
62 |
63 | public static final String PREF_FORMAT = "format";
64 | public static final String VALUE_FORMAT_GPX = "gpx";
65 | public static final String VALUE_FORMAT_KML = "kml";
66 | public static final String PREF_FORMAT_DEFAULT = VALUE_FORMAT_GPX;
67 |
68 | public static final String PREF_TRACK_MERGE_GAP = "track_merge_gap";
69 | public static final int PREF_TRACK_MERGE_GAP_DEFAULT = 900;
70 |
71 | public static final String PREF_DATETIME_START = "datetime_start";
72 | public static final long PREF_DATETIME_START_DEFAULT = -1;
73 |
74 | public static final String PREF_DATETIME_END = "datetime_end";
75 | public static final long PREF_DATETIME_END_DEFAULT = -1;
76 |
77 | public static final String PREF_TRACK_MIN_POINTS = "track_min_points";
78 | public static final String PREF_TRACK_MIN_TIME = "track_min_time";
79 | public static final String PREF_TRACK_MIN_DISTANCE = "track_min_distance";
80 | public static final int PREF_TRACK_MIN_POINTS_DEFAULT = 5;
81 | public static final int PREF_TRACK_MIN_TIME_DEFAULT = 60;
82 | public static final int PREF_TRACK_MIN_DISTANCE_DEFAULT = 1000;
83 |
84 | public static final String PREF_ACC_LP_UNKNOWN = "acc_lp_unknown";
85 | public static final String PREF_ACC_LP_STILL = "acc_lp_still";
86 | public static final String PREF_ACC_LP_FOOT = "acc_lp_foot";
87 | public static final String PREF_ACC_LP_BICYCLE = "acc_lp_bicycle";
88 | public static final String PREF_ACC_LP_VEHICLE = "acc_lp_vehicle";
89 | public static final String PREF_ACC_HA_UNKNOWN = "acc_ha_unknown";
90 | public static final String PREF_ACC_HA_STILL = "acc_ha_still";
91 | public static final String PREF_ACC_HA_FOOT = "acc_ha_foot";
92 | public static final String PREF_ACC_HA_BICYCLE = "acc_ha_bicycle";
93 | public static final String PREF_ACC_HA_VEHICLE = "acc_ha_vehicle";
94 |
95 | public static final long PREF_ACC_LP_UNKNOWN_DEFAULT = 1600;
96 | public static final long PREF_ACC_LP_STILL_DEFAULT = 200;
97 | public static final long PREF_ACC_LP_FOOT_DEFAULT = 400;
98 | public static final long PREF_ACC_LP_BICYCLE_DEFAULT = 800;
99 | public static final long PREF_ACC_LP_VEHICLE_DEFAULT = 1600;
100 | public static final long PREF_ACC_HA_UNKNOWN_DEFAULT = 800;
101 | public static final long PREF_ACC_HA_STILL_DEFAULT = 100;
102 | public static final long PREF_ACC_HA_FOOT_DEFAULT = 200;
103 | public static final long PREF_ACC_HA_BICYCLE_DEFAULT = 400;
104 | public static final long PREF_ACC_HA_VEHICLE_DEFAULT = 800;
105 |
106 | public static void launchActivity(Activity activity, long id) {
107 | Intent i = new Intent(activity, ExportActivity.class);
108 | i.putExtra(ExportActivity.EXTRA_LOG_ID, id);
109 | activity.startActivityForResult(i, 0);
110 | }
111 |
112 | private SharedPreferences prefs = null;
113 | private ListPreference prefFormat = null;
114 | private EditTextPreference prefMergeTrackGap = null;
115 | private DateTimePickerPreference prefDateStart = null;
116 | private DateTimePickerPreference prefDateEnd = null;
117 | private EditTextPreference prefTrackMinPoints = null;
118 | private EditTextPreference prefTrackMinTime = null;
119 | private DistanceEditTextPreference prefTrackMinDistance = null;
120 | private DistanceEditTextPreference prefAccLPUnknown = null;
121 | private DistanceEditTextPreference prefAccLPStill = null;
122 | private DistanceEditTextPreference prefAccLPFoot = null;
123 | private DistanceEditTextPreference prefAccLPBicycle = null;
124 | private DistanceEditTextPreference prefAccLPVehicle = null;
125 | private DistanceEditTextPreference prefAccHAUnknown = null;
126 | private DistanceEditTextPreference prefAccHAStill = null;
127 | private DistanceEditTextPreference prefAccHAFoot = null;
128 | private DistanceEditTextPreference prefAccHABicycle = null;
129 | private DistanceEditTextPreference prefAccHAVehicle = null;
130 |
131 | private volatile long logid = 0;
132 |
133 | private volatile long dateFirst = -1;
134 | private volatile long dateLast = -1;
135 |
136 | private volatile boolean metric = true;
137 |
138 | private volatile boolean doneLoading = false;
139 | private volatile View progressBar = null;
140 |
141 | @Override
142 | protected void onCreate(Bundle savedInstanceState) {
143 | super.onCreate(savedInstanceState);
144 | setResult(RESULT_OK);
145 |
146 | logid = 0;
147 | if (getIntent() != null) {
148 | logid = getIntent().getLongExtra(EXTRA_LOG_ID, 0);
149 | } else if (savedInstanceState != null) {
150 | logid = savedInstanceState.getLong(EXTRA_LOG_ID, 0);
151 | }
152 |
153 | setTitle(getString(R.string.export_preference_title));
154 | getListView().setVisibility(View.GONE);
155 |
156 | LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
157 | if (getListView().getParent() instanceof LinearLayout) {
158 | progressBar = inflater.inflate(R.layout.progressbar, null);
159 | ((LinearLayout)(getListView().getParent())).addView(progressBar);
160 | }
161 |
162 | prefs = PreferenceManager.getDefaultSharedPreferences(ExportActivity.this);
163 | updateRecordCount(true);
164 | }
165 |
166 | @Override
167 | protected void onDestroy() {
168 | prefs.unregisterOnSharedPreferenceChangeListener(preferencesListener);
169 | super.onDestroy();
170 | }
171 |
172 | @Override
173 | public boolean onCreateOptionsMenu(Menu menu) {
174 | menu.
175 | add(R.string.menu_export).
176 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
177 | @Override
178 | public boolean onMenuItemClick(MenuItem item) {
179 | if (!doneLoading) return false;
180 |
181 | Exporter exporter = new Exporter(ExportActivity.this);
182 | exporter.setFormat(prefs.getString(PREF_FORMAT, PREF_FORMAT_DEFAULT).equals(VALUE_FORMAT_GPX) ? Format.GPX : Format.KML);
183 | exporter.setTrackMergeGap(Long.parseLong(prefs.getString(PREF_TRACK_MERGE_GAP, String.valueOf(PREF_TRACK_MERGE_GAP_DEFAULT)), 10));
184 | exporter.setDateStart(prefs.getLong(PREF_DATETIME_START, PREF_DATETIME_START_DEFAULT));
185 | exporter.setDateEnd(prefs.getLong(PREF_DATETIME_END, PREF_DATETIME_END_DEFAULT));
186 | exporter.setTrackMinPoints(Long.parseLong(prefs.getString(PREF_TRACK_MIN_POINTS, String.valueOf(PREF_TRACK_MIN_POINTS_DEFAULT)), 10));
187 | exporter.setTrackMinTime(Long.parseLong(prefs.getString(PREF_TRACK_MIN_TIME, String.valueOf(PREF_TRACK_MIN_TIME_DEFAULT)), 10));
188 | exporter.setTrackMinDistance(Long.parseLong(prefs.getString(PREF_TRACK_MIN_DISTANCE, String.valueOf(PREF_TRACK_MIN_DISTANCE_DEFAULT)), 10));
189 | exporter.setAccuracyLowPowerUnknown(Long.parseLong(prefs.getString(PREF_ACC_LP_UNKNOWN, String.valueOf(PREF_ACC_LP_UNKNOWN_DEFAULT)), 10));
190 | exporter.setAccuracyLowPowerStill(Long.parseLong(prefs.getString(PREF_ACC_LP_STILL, String.valueOf(PREF_ACC_LP_STILL_DEFAULT)), 10));
191 | exporter.setAccuracyLowPowerFoot(Long.parseLong(prefs.getString(PREF_ACC_LP_FOOT, String.valueOf(PREF_ACC_LP_FOOT_DEFAULT)), 10));
192 | exporter.setAccuracyLowPowerBicycle(Long.parseLong(prefs.getString(PREF_ACC_LP_BICYCLE, String.valueOf(PREF_ACC_LP_BICYCLE_DEFAULT)), 10));
193 | exporter.setAccuracyLowPowerVehicle(Long.parseLong(prefs.getString(PREF_ACC_LP_VEHICLE, String.valueOf(PREF_ACC_LP_VEHICLE_DEFAULT)), 10));
194 | exporter.setAccuracyHighAccuracyUnknown(Long.parseLong(prefs.getString(PREF_ACC_HA_UNKNOWN, String.valueOf(PREF_ACC_HA_UNKNOWN_DEFAULT)), 10));
195 | exporter.setAccuracyHighAccuracyStill(Long.parseLong(prefs.getString(PREF_ACC_HA_STILL, String.valueOf(PREF_ACC_HA_STILL_DEFAULT)), 10));
196 | exporter.setAccuracyHighAccuracyFoot(Long.parseLong(prefs.getString(PREF_ACC_HA_FOOT, String.valueOf(PREF_ACC_HA_FOOT_DEFAULT)), 10));
197 | exporter.setAccuracyHighAccuracyBicycle(Long.parseLong(prefs.getString(PREF_ACC_HA_BICYCLE, String.valueOf(PREF_ACC_HA_BICYCLE_DEFAULT)), 10));
198 | exporter.setAccuracyHighAccuracyVehicle(Long.parseLong(prefs.getString(PREF_ACC_HA_VEHICLE, String.valueOf(PREF_ACC_HA_VEHICLE_DEFAULT)), 10));
199 | exporter.export(getQuery(false));
200 | return true;
201 | }
202 | }).
203 | setIcon(R.drawable.ic_action_export).
204 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
205 | menu.
206 | add(R.string.menu_cancel).
207 | setOnMenuItemClickListener(new OnMenuItemClickListener() {
208 | @Override
209 | public boolean onMenuItemClick(MenuItem item) {
210 | finish();
211 | return false;
212 | }
213 | }).
214 | setIcon(R.drawable.ic_action_cancel).
215 | setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
216 | return true;
217 | }
218 |
219 | @Override
220 | protected void onSaveInstanceState(Bundle outState) {
221 | outState.putLong(EXTRA_LOG_ID, logid);
222 | super.onSaveInstanceState(outState);
223 | }
224 |
225 | @Override
226 | protected void onUserLeaveHint() {
227 | finish();
228 | super.onUserLeaveHint();
229 | }
230 |
231 | @Override
232 | public void onBackPressed() {
233 | finish();
234 | super.onBackPressed();
235 | }
236 |
237 | private String formatValue(String value) {
238 | return String.format(Locale.ENGLISH, "[ %s ]", value);
239 | }
240 |
241 | private String formatValue(int interval) {
242 | if (interval == 0) return formatValue(getString(R.string.profile_preference_format_disabled));
243 |
244 | int hours = interval / 3600;
245 | interval %= 3600;
246 | int minutes = interval / 60;
247 | interval %= 60;
248 | int seconds = interval;
249 |
250 | StringBuilder b = new StringBuilder();
251 | if (hours > 0) {
252 | b.append(String.format(Locale.ENGLISH, "%d:%02d:%02d", hours, minutes, seconds));
253 | } else {
254 | b.append(String.format(Locale.ENGLISH, "%d:%02d", minutes, seconds));
255 | }
256 | b.append(" - ");
257 | if (hours > 0) {
258 | b.append(String.format(Locale.ENGLISH, getString(R.string.profile_preference_format_interval_hours), hours, minutes, seconds));
259 | } else {
260 | b.append(String.format(Locale.ENGLISH, getString(R.string.profile_preference_format_interval), minutes, seconds));
261 | }
262 |
263 | return formatValue(b.toString());
264 | }
265 |
266 | private DistanceEditTextPreference editDistance(Context context, PreferenceCategory category, int caption, int summary, int dialogCaption, String key, Object defaultValue, boolean enabled) {
267 | DistanceEditTextPreference retval = new DistanceEditTextPreference(context);
268 | retval.setMetric(metric);
269 | if (caption > 0) retval.setTitle(caption);
270 | if (summary > 0) retval.setSummary(summary);
271 | retval.setEnabled(enabled);
272 | retval.setKey(key);
273 | retval.setDefaultValue(defaultValue);
274 | if (dialogCaption > 0) retval.setDialogTitle(dialogCaption);
275 | retval.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
276 | if (category != null) category.addPreference(retval);
277 | return retval;
278 | }
279 |
280 | private PreferenceScreen createPreferenceHierarchy() {
281 | metric = !prefs.getString(SettingsFragment.PREF_UNITS, SettingsFragment.PREF_UNITS_DEFAULT).equals(SettingsFragment.VALUE_UNITS_IMPERIAL);
282 |
283 | PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
284 |
285 | prefFormat = Pref.List(
286 | this,
287 | null,
288 | R.string.export_preference_format_title,
289 | 0,
290 | R.string.export_preference_format_popup,
291 | PREF_FORMAT,
292 | PREF_FORMAT_DEFAULT,
293 | new String[] {
294 | getString(R.string.export_preference_format_gpx),
295 | getString(R.string.export_preference_format_kml)
296 | },
297 | new String[] {
298 | VALUE_FORMAT_GPX,
299 | VALUE_FORMAT_KML
300 | },
301 | true
302 | );
303 | root.addPreference(prefFormat);
304 |
305 | prefMergeTrackGap = Pref.Edit(
306 | this,
307 | null,
308 | R.string.export_preference_track_merge_gap_title,
309 | 0,
310 | R.string.export_preference_track_merge_gap_popup,
311 | PREF_TRACK_MERGE_GAP,
312 | String.valueOf(PREF_TRACK_MERGE_GAP_DEFAULT),
313 | true,
314 | InputType.TYPE_CLASS_NUMBER
315 | );
316 | root.addPreference(prefMergeTrackGap);
317 |
318 | PreferenceCategory catDateFilter = Pref.Category(this, root, R.string.export_preference_datefilter_category);
319 |
320 | prefDateStart = new DateTimePickerPreference(this, prefs, PREF_DATETIME_START, PREF_DATETIME_START_DEFAULT, dateFirst, dateLast, false);
321 | prefDateStart.setTitle(getString(R.string.export_preference_date_start_title));
322 | prefDateStart.setSummary("");
323 | prefDateStart.setPopupTitle(getString(R.string.export_preference_date_start_popup));
324 | catDateFilter.addPreference(prefDateStart.getPreference());
325 |
326 | prefDateEnd = new DateTimePickerPreference(this, prefs, PREF_DATETIME_END, PREF_DATETIME_END_DEFAULT, dateFirst, dateLast, true);
327 | prefDateEnd.setTitle(getString(R.string.export_preference_date_end_title));
328 | prefDateEnd.setSummary("");
329 | prefDateEnd.setPopupTitle(getString(R.string.export_preference_date_end_popup));
330 | catDateFilter.addPreference(prefDateEnd.getPreference());
331 |
332 | int popup = metric ? R.string.export_preference_accuracy_popup_meters : R.string.export_preference_accuracy_popup_feet;
333 |
334 | PreferenceCategory catTrack = Pref.Category(this, root, R.string.export_preference_trackfilter_category);
335 | prefTrackMinPoints = Pref.Edit(this, catTrack, R.string.export_preference_track_min_points_title, 0, R.string.export_preference_track_min_points_popup, PREF_TRACK_MIN_POINTS, String.valueOf(PREF_TRACK_MIN_POINTS_DEFAULT), true, InputType.TYPE_CLASS_NUMBER);
336 | prefTrackMinTime = Pref.Edit(this, catTrack, R.string.export_preference_track_min_time_title, 0, R.string.export_preference_track_min_time_popup, PREF_TRACK_MIN_TIME, String.valueOf(PREF_TRACK_MIN_TIME_DEFAULT), true, InputType.TYPE_CLASS_NUMBER);
337 | prefTrackMinDistance = editDistance(this, catTrack, R.string.export_preference_track_min_distance_title, 0, popup, PREF_TRACK_MIN_DISTANCE, String.valueOf(PREF_TRACK_MIN_DISTANCE_DEFAULT), true);
338 |
339 | PreferenceCategory catAccLP = Pref.Category(this, root, R.string.export_preference_lowpowerfilter_category);
340 | prefAccLPUnknown = editDistance(this, catAccLP, R.string.profile_preference_caption_unknown, 0, popup, PREF_ACC_LP_UNKNOWN, String.valueOf(PREF_ACC_LP_UNKNOWN_DEFAULT), true);
341 | prefAccLPStill = editDistance(this, catAccLP, R.string.profile_preference_caption_still, 0, popup, PREF_ACC_LP_STILL, String.valueOf(PREF_ACC_LP_STILL_DEFAULT), true);
342 | prefAccLPFoot = editDistance(this, catAccLP, R.string.profile_preference_caption_foot, 0, popup, PREF_ACC_LP_FOOT, String.valueOf(PREF_ACC_LP_FOOT_DEFAULT), true);
343 | prefAccLPBicycle = editDistance(this, catAccLP, R.string.profile_preference_caption_bicycle, 0, popup, PREF_ACC_LP_BICYCLE, String.valueOf(PREF_ACC_LP_BICYCLE_DEFAULT), true);
344 | prefAccLPVehicle = editDistance(this, catAccLP, R.string.profile_preference_caption_vehicle, 0, popup, PREF_ACC_LP_VEHICLE, String.valueOf(PREF_ACC_LP_VEHICLE_DEFAULT), true);
345 |
346 | PreferenceCategory catAccHA = Pref.Category(this, root, R.string.export_preference_highaccuracyfilter_category);
347 | prefAccHAUnknown = editDistance(this, catAccHA, R.string.profile_preference_caption_unknown, 0, popup, PREF_ACC_HA_UNKNOWN, String.valueOf(PREF_ACC_HA_UNKNOWN_DEFAULT), true);
348 | prefAccHAStill = editDistance(this, catAccHA, R.string.profile_preference_caption_still, 0, popup, PREF_ACC_HA_STILL, String.valueOf(PREF_ACC_HA_STILL_DEFAULT), true);
349 | prefAccHAFoot = editDistance(this, catAccHA, R.string.profile_preference_caption_foot, 0, popup, PREF_ACC_HA_FOOT, String.valueOf(PREF_ACC_HA_FOOT_DEFAULT), true);
350 | prefAccHABicycle = editDistance(this, catAccHA, R.string.profile_preference_caption_bicycle, 0, popup, PREF_ACC_HA_BICYCLE, String.valueOf(PREF_ACC_HA_BICYCLE_DEFAULT), true);
351 | prefAccHAVehicle = editDistance(this, catAccHA, R.string.profile_preference_caption_vehicle, 0, popup, PREF_ACC_HA_VEHICLE, String.valueOf(PREF_ACC_HA_VEHICLE_DEFAULT), true);
352 |
353 | updatePrefs(null);
354 |
355 | doneLoading = true;
356 | if (progressBar != null) progressBar.setVisibility(View.GONE);
357 | getListView().setVisibility(View.VISIBLE);
358 |
359 | return root;
360 | }
361 |
362 | private SharedPreferences.OnSharedPreferenceChangeListener preferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
363 | @Override
364 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
365 | updatePrefs(key);
366 | }
367 | };
368 |
369 | private Cursor getQuery(boolean all) {
370 | ArrayList conditions = new ArrayList();
371 | ArrayList parameters = new ArrayList();
372 |
373 | long startDate = -1;
374 | long endDate = -1;
375 |
376 | if (!all) {
377 | startDate = prefs.getLong(PREF_DATETIME_START, PREF_DATETIME_START_DEFAULT);
378 | endDate = prefs.getLong(PREF_DATETIME_END, PREF_DATETIME_END_DEFAULT);
379 | }
380 |
381 | if (startDate >= 0) {
382 | conditions.add(Database.Location.COLUMN_NAME_TIME + " >= ?");
383 | parameters.add(String.valueOf(startDate));
384 | }
385 |
386 | if (endDate >= 0) {
387 | conditions.add(Database.Location.COLUMN_NAME_TIME + " <= ?");
388 | parameters.add(String.valueOf(endDate));
389 | }
390 |
391 | String selection = "";
392 | for (int i = 0; i < conditions.size(); i++) {
393 | selection += conditions.get(i);
394 | if (i < conditions.size() - 1) selection += " AND ";
395 | }
396 |
397 | return Database.Helper.getInstance(this).getReadableDatabase().query(
398 | Database.Location.TABLE_NAME,
399 | null,
400 | selection,
401 | parameters.toArray(new String[parameters.size()]),
402 | null,
403 | null,
404 | null
405 | );
406 | }
407 |
408 | private void updateRecordCount(boolean startup) {
409 | if (startup) {
410 | (new RecordCountAsync()).execute(0);
411 | } else {
412 | (new RecordCountAsync()).execute();
413 | }
414 | }
415 |
416 | private void updatePrefs(String key) {
417 | if ((key == null) || (key.equals(PREF_FORMAT))) {
418 | String val = prefs.getString(PREF_FORMAT, PREF_FORMAT_DEFAULT);
419 | if (val.equals(VALUE_FORMAT_GPX)) prefFormat.setSummary(formatValue(getString(R.string.export_preference_format_gpx)));
420 | if (val.equals(VALUE_FORMAT_KML)) prefFormat.setSummary(formatValue(getString(R.string.export_preference_format_kml)));
421 | }
422 |
423 | if ((key == null) || (key.equals(PREF_TRACK_MERGE_GAP))) {
424 | prefMergeTrackGap.setSummary(formatValue(Integer.parseInt(prefs.getString(PREF_TRACK_MERGE_GAP, String.valueOf(PREF_TRACK_MERGE_GAP_DEFAULT)), 10)));
425 | }
426 |
427 | if ((key == null) || (key.equals(PREF_DATETIME_START))) {
428 | long date = prefs.getLong(PREF_DATETIME_START, PREF_DATETIME_START_DEFAULT);
429 | if (date == -1) {
430 | prefDateStart.setSummary(formatValue(getString(R.string.export_preference_date_disabled)));
431 | } else {
432 | prefDateStart.setSummary(formatValue(
433 | DateFormat.getMediumDateFormat(this).format(new Date(date)) +
434 | ", " +
435 | DateFormat.getTimeFormat(this).format(new Date(date))
436 | ));
437 | }
438 | }
439 |
440 | if ((key == null) || (key.equals(PREF_DATETIME_END))) {
441 | long date = prefs.getLong(PREF_DATETIME_END, PREF_DATETIME_END_DEFAULT);
442 | if (date == -1) {
443 | prefDateEnd.setSummary(formatValue(getString(R.string.export_preference_date_disabled)));
444 | } else {
445 | prefDateEnd.setSummary(formatValue(
446 | DateFormat.getMediumDateFormat(this).format(new Date(date)) +
447 | ", " +
448 | DateFormat.getTimeFormat(this).format(new Date(date))
449 | ));
450 | }
451 | }
452 |
453 | if ((key == null) || (key.equals(PREF_TRACK_MIN_POINTS))) {
454 | prefTrackMinPoints.setSummary(formatValue(String.valueOf(prefs.getString(PREF_TRACK_MIN_POINTS, String.valueOf(PREF_TRACK_MIN_POINTS_DEFAULT)))));
455 | }
456 |
457 | if ((key == null) || (key.equals(PREF_TRACK_MIN_TIME))) {
458 | prefTrackMinTime.setSummary(formatValue(Integer.parseInt(prefs.getString(PREF_TRACK_MIN_TIME, String.valueOf(PREF_TRACK_MIN_TIME_DEFAULT)), 10)));
459 | }
460 |
461 | for (EditTextPreference pref : new EditTextPreference[] {
462 | prefAccLPUnknown, prefAccLPStill, prefAccLPFoot, prefAccLPBicycle, prefAccLPVehicle,
463 | prefAccHAUnknown, prefAccHAStill, prefAccHAFoot, prefAccHABicycle, prefAccHAVehicle,
464 | prefTrackMinDistance
465 | }) {
466 | if ((key == null) || (key.equals(pref.getKey()))) {
467 | long val = Long.parseLong(prefs.getString(pref.getKey(), pref.getText()), 10);
468 | if (val > 0) {
469 | if (metric) {
470 | pref.setSummary(formatValue(String.format(getString(R.string.export_preference_accuracy_format_meters), val)));
471 | } else {
472 | pref.setSummary(formatValue(String.format(getString(R.string.export_preference_accuracy_format_feet), (int)((float)val * SettingsFragment.METER_FEET_RATIO))));
473 | }
474 | } else {
475 | pref.setSummary(R.string.profile_preference_format_disabled);
476 | }
477 | }
478 | }
479 |
480 | //if (key != null) updateRecordCount(false); we're not doing this
481 | }
482 |
483 | private class RecordCountAsync extends AsyncTask {
484 | private volatile boolean getDates = false;
485 |
486 | @Override
487 | protected Integer doInBackground(Integer... arg0) {
488 | getDates = (arg0.length > 0);
489 |
490 | Cursor c = getQuery(getDates);
491 | try {
492 | int count = c.getCount();
493 |
494 | if (getDates && (count > 0)) {
495 | Database.Location loc = new Database.Location();
496 |
497 | c.moveToFirst();
498 | loc.loadFromCursor(c);
499 | dateFirst = loc.getTime();
500 |
501 | c.moveToLast();
502 | loc.loadFromCursor(c);
503 | dateLast = loc.getTime();
504 | }
505 |
506 | return count;
507 | } finally {
508 | c.close();
509 | }
510 | }
511 |
512 | @Override
513 | protected void onPostExecute(Integer result) {
514 | if (getDates) {
515 | setPreferenceScreen(createPreferenceHierarchy());
516 | prefs.registerOnSharedPreferenceChangeListener(preferencesListener);
517 | }
518 | }
519 | }
520 |
521 | @SuppressWarnings("unused")
522 | private class DateTimePickerPreference implements OnPreferenceClickListener {
523 | private Context context = null;
524 | private long datetime = -1;
525 | private String key = null;
526 | private Preference preference = null;
527 | private SharedPreferences prefs = null;
528 | private CharSequence popuptitle = "";
529 | private boolean is24hour = false;
530 | private boolean isEnd = false;
531 | private long minValue = -1;
532 | private long maxValue = -1;
533 |
534 | public DateTimePickerPreference(Context context, SharedPreferences prefs, String key, long defaultValue, long minValue, long maxValue, boolean isEnd) {
535 | this.context = context;
536 | this.prefs = prefs;
537 | this.key = key;
538 | if (key != null) {
539 | this.datetime = prefs.getLong(key, defaultValue);
540 | } else {
541 | this.datetime = defaultValue;
542 | }
543 | this.preference = new Preference(context);
544 | preference.setOnPreferenceClickListener(this);
545 | is24hour = DateFormat.is24HourFormat(context);
546 | this.isEnd = isEnd;
547 | this.minValue = minValue;
548 | this.maxValue = maxValue;
549 | }
550 |
551 | public void setDate(long datetime) {
552 | this.datetime = datetime;
553 | if (key != null) prefs.edit().putLong(key, datetime).commit();
554 | }
555 |
556 | public long getDate() {
557 | return this.datetime;
558 | }
559 |
560 | public Preference getPreference() {
561 | return this.preference;
562 | }
563 |
564 | public void setTitle(CharSequence title) {
565 | this.preference.setTitle(title);
566 | }
567 |
568 | public void setSummary(CharSequence summary) {
569 | this.preference.setSummary(summary);
570 | }
571 |
572 | public void setPopupTitle(CharSequence popuptitle) {
573 | this.popuptitle = popuptitle;
574 | }
575 |
576 | @Override
577 | public boolean onPreferenceClick(Preference preference) {
578 | LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
579 | View v = inflater.inflate(R.layout.dialog_datetime, null);
580 |
581 | Calendar cal = Calendar.getInstance();
582 | if (datetime >= 0) {
583 | cal.setTimeInMillis(datetime);
584 | } else if (!isEnd && (minValue >= 0)) {
585 | cal.setTimeInMillis(minValue);
586 | } else if (isEnd && (maxValue >= 0)) {
587 | cal.setTimeInMillis(maxValue);
588 | }
589 |
590 | final DatePicker datePicker = (DatePicker)v.findViewById(R.id.datePicker);
591 | if (minValue >= 0) datePicker.setMinDate(minValue);
592 | if (maxValue >= 0) datePicker.setMaxDate(maxValue);
593 | datePicker.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));
594 |
595 | final TimePicker timePicker = (TimePicker)v.findViewById(R.id.timePicker);
596 | timePicker.setIs24HourView(is24hour);
597 | timePicker.setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
598 | timePicker.setCurrentMinute(cal.get(Calendar.MINUTE));
599 |
600 | (new AlertDialog.Builder(context)).
601 | setTitle(popuptitle).
602 | setView(v).
603 | setPositiveButton(R.string.generic_ok, new OnClickListener() {
604 | @Override
605 | public void onClick(DialogInterface dialog, int which) {
606 | Calendar cal = Calendar.getInstance();
607 | cal.set(Calendar.YEAR, datePicker.getYear());
608 | cal.set(Calendar.MONTH, datePicker.getMonth());
609 | cal.set(Calendar.DAY_OF_MONTH, datePicker.getDayOfMonth());
610 | cal.set(Calendar.HOUR_OF_DAY, timePicker.getCurrentHour());
611 | cal.set(Calendar.MINUTE, timePicker.getCurrentMinute());
612 | if (!isEnd) {
613 | cal.set(Calendar.SECOND, 0);
614 | cal.set(Calendar.MILLISECOND, 0);
615 | } else {
616 | cal.set(Calendar.SECOND, 59);
617 | cal.set(Calendar.MILLISECOND, 999);
618 | }
619 | setDate(cal.getTimeInMillis());
620 | }
621 | }).
622 | setNeutralButton(R.string.generic_disable, new OnClickListener() {
623 | @Override
624 | public void onClick(DialogInterface dialog, int which) {
625 | setDate(-1);
626 | }
627 | }).
628 | setNegativeButton(R.string.generic_cancel, null).
629 | setCancelable(true).
630 | show();
631 |
632 | return true;
633 | }
634 | }
635 |
636 | @SuppressWarnings("unused")
637 | private class DistanceEditTextPreference extends EditTextPreference {
638 | private boolean isMetric = true;
639 |
640 | public DistanceEditTextPreference(Context context) {
641 | super(context);
642 | }
643 |
644 | public void setMetric(boolean isMetric) {
645 | this.isMetric = isMetric;
646 | }
647 |
648 | public boolean getMetric() {
649 | return isMetric;
650 | }
651 |
652 | private String toFeet(String value) {
653 | if (isMetric) return value;
654 |
655 | int val = Integer.parseInt(value, 10);
656 | val = (int)((float)val * SettingsFragment.METER_FEET_RATIO);
657 | return String.valueOf(val);
658 | }
659 |
660 | private String fromFeet(String value) {
661 | if (isMetric) return value;
662 |
663 | int val = Integer.parseInt(value, 10);
664 | val = (int)((float)val / SettingsFragment.METER_FEET_RATIO);
665 | return String.valueOf(val);
666 | }
667 |
668 | @Override
669 | protected Object onGetDefaultValue(TypedArray a, int index) {
670 | return toFeet(a.getString(index));
671 | }
672 |
673 | @Override
674 | protected boolean persistString(String value) {
675 | return super.persistString(fromFeet(value));
676 | }
677 |
678 | @Override
679 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
680 | setText(toFeet(restoreValue ? getPersistedString(getText()) : (String) defaultValue));
681 | }
682 | }
683 | }
684 |
--------------------------------------------------------------------------------