init commit

master
keith24 2016-03-21 20:30:53 -04:00
commit a81902980b
179 changed files with 3307 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

BIN
app/app-release.apk Normal file

Binary file not shown.

BIN
app/keystore.jks Normal file

Binary file not shown.

29
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,29 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/keith/.AndroidStudio1.4/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
#-keep class org.codehaus.** {*;}
#-dontwarn -dontwarn org.codehaus.**
#-keep class com.ocpsoft.** {*;}
#-dontwarn com.ocpsoft.**
#-keep class java.nio.** {*;}
#-dontwarn java.nio.**
#-dontwarn com.squareup.**
#-dontwarn org.json.**
-dontwarn *
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,13 @@
package us.keithirwin.tracman;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="us.keithirwin.tracman"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="@string/version_code"
android:versionName="@string/version">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:icon="@drawable/logo_by"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:parentActivityName=".LoginActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="us.keithirwin.tracman.LoginActivity"/>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/settings_name"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
<service
android:name=".LocationService"
android:exported="false">
</service>
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,109 @@
package us.keithirwin.tracman;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -0,0 +1,20 @@
package us.keithirwin.tracman;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class BootReceiver extends BroadcastReceiver {
public BootReceiver() {}
@Override
public void onReceive(Context context, Intent intent) {
// Starts location service on boot
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("pref_start_boot", true)) {
context.startService(new Intent(context, LocationService.class));
}
}
}

View File

@ -0,0 +1,247 @@
package us.keithirwin.tracman;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener {
public LocationService() {}
private String TAG = "LocationService";
private Socket mSocket;
private String mUserID;
private SharedPreferences sharedPref;
Location mLastLocation;
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
@Nullable
private NotificationManager mNotificationManager;
private final NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this);
private void setupNotifications(Boolean persist) {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
PendingIntent notificationIntent = PendingIntent.getActivity(this, 0,
new Intent(this, SettingsActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP),
0);
mNotificationBuilder
.setPriority(-1)
.setSmallIcon(R.drawable.logo_white)
// .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.logo_by))
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(getText(R.string.app_name))
// .setWhen(System.currentTimeMillis())
.setContentIntent(notificationIntent)
.setOngoing(persist);
}
private void showNotification(CharSequence text, Boolean active) {
mNotificationBuilder
.setTicker(text)
.setContentText(text);
if (active) {
mNotificationBuilder.setSmallIcon(R.drawable.logo_white);
} else {
mNotificationBuilder.setSmallIcon(R.drawable.logo_trans);
}
if (mNotificationManager != null) {
mNotificationManager.notify(1, mNotificationBuilder.build());
}
}
private final BroadcastReceiver LowPowerReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
connectLocationUpdates(300, LocationRequest.PRIORITY_NO_POWER);
Log.d(TAG, "Priority and interval lowered due to low power");
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate called");
// Get preferences
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
setupNotifications(true);
showNotification(getText(R.string.connecting), false);
Log.d(TAG, "Notification set up");
buildGoogleApiClient();
Log.d(TAG, "Google API Client built");
mGoogleApiClient.connect();
Log.d(TAG, "Connected to Google API Client");
IntentFilter lowPowerFilter = new IntentFilter();
lowPowerFilter.addAction("android.intent.action.BATTERY_LOW");
registerReceiver(LowPowerReceiver, lowPowerFilter);
Log.d(TAG, "LowPowerReceiver activated");
mUserID = sharedPref.getString("loggedInUserId", null);
final String SERVER_ADDRESS = "https://tracman.org/";
// Connect to socket
try {
mSocket = IO.socket(SERVER_ADDRESS);
mSocket.on("activate", onActivate);
mSocket.connect();
mSocket.emit("room", "app-"+mUserID);
Log.d(TAG, "Connected to socket.io server "+SERVER_ADDRESS);
} catch (URISyntaxException e) {
showNotification(getText(R.string.server_connection_error), false);
Log.e(TAG, "Failed to connect to sockets server " + SERVER_ADDRESS, e);
}
showNotification(getText(R.string.connected), false);
}
private int getPrioritySetting() {
return Integer.parseInt(sharedPref.getString("broadcast_priority", "100"));
}
private int getIntervalSetting() {
return Integer.parseInt(
sharedPref.getString("broadcast_frequency",
getResources().getString(R.string.pref_default_broadcast_frequency)));
}
void connectLocationUpdates(Integer interval, Integer priority) {
if (mLocationRequest != null) {
mLocationRequest.setPriority(priority);
mLocationRequest.setInterval(interval * 1000); // 1000 = 1 second
} else{
mLocationRequest = LocationRequest.create();
connectLocationUpdates(getIntervalSetting(), getPrioritySetting());
}
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient,
mLocationRequest,
this);
} else {
mGoogleApiClient.connect();
}
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (mLastLocation != null) {
onLocationChanged(mLastLocation);
}
}
@Override
public void onConnected(Bundle bundle) {
Log.d(TAG, "onConnected called");
mLocationRequest = LocationRequest.create();
connectLocationUpdates(getIntervalSetting(), getPrioritySetting());
showNotification(getString(R.string.realtime_updates), true);
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "onConnectionFailed: " + connectionResult);
showNotification(getText(R.string.google_connection_error), false);
buildGoogleApiClient();
}
private Emitter.Listener onActivate = new Emitter.Listener() {
@Override
public void call(final Object... args) {
if (args[0].toString().equals("true")) {
Log.d(TAG, "Activating realtime updates");
connectLocationUpdates(getIntervalSetting(), getPrioritySetting());
showNotification(getString(R.string.realtime_updates), true);
} else {
Log.d(TAG, "Deactivating realtime updates");
connectLocationUpdates(300, LocationRequest.PRIORITY_NO_POWER);
showNotification(getString(R.string.occasional_updates), false);
}
}
};
@Override
public void onLocationChanged(Location location) {
JSONObject mLocationView = new JSONObject();
try {
mLocationView.put("usr", mUserID);
mLocationView.put("lat", String.valueOf(location.getLatitude()));
mLocationView.put("lon", String.valueOf(location.getLongitude()));
mLocationView.put("dir", String.valueOf(location.getBearing()));
mLocationView.put("spd", String.valueOf(location.getSpeed()));
} catch (JSONException e) {
Log.e(TAG, "Failed to put JSON data");
}
mSocket.emit("app", mLocationView);
// Log.v(TAG, "Location updated: " + mLocationView.toString());
}
@Override
public void onConnectionSuspended(int i) {
Log.d(TAG, "onConnectionSuspended called");
showNotification(getText(R.string.google_connection_error), false);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy executed");
mSocket.disconnect();
Log.d(TAG, "Disconnected from sockets");
mGoogleApiClient.disconnect();
Log.d(TAG, "Google API disconnected");
unregisterReceiver(LowPowerReceiver);
Log.d(TAG, "LowPowerReceiver deactivated");
setupNotifications(false);
showNotification(getText(R.string.disconnected), false);
Log.d(TAG, "Notification changed");
}
}

View File

@ -0,0 +1,239 @@
package us.keithirwin.tracman;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.OptionalPendingResult;
import com.google.android.gms.common.api.ResultCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Callback;
import okhttp3.Response;
public class LoginActivity extends AppCompatActivity implements
GoogleApiClient.OnConnectionFailedListener,
View.OnClickListener {
private static final String TAG = "SignInActivity";
private static final int RC_SIGN_IN = 9001;
private final String SERVER_ADDRESS = "https://tracman.org/";
private static final String GOOGLE_WEB_CLIENT_ID = "483494341936-hrn0ms1tebgdtfs5f4i6ebmkt3qmo16o.apps.googleusercontent.com";
private GoogleApiClient mGoogleApiClient;
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set up layout
setContentView(R.layout.activity_login);
setTitle(R.string.login_name);
TextView tv = (TextView) findViewById(R.id.login_description);
tv.setMovementMethod(LinkMovementMethod.getInstance());
// Configure sign-in to request the user's ID and basic profile, included in DEFAULT_SIGN_IN.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(GOOGLE_WEB_CLIENT_ID)
.build();
// Set up buttons
SignInButton signInButton = (SignInButton) findViewById(R.id.google_sign_in_button);
signInButton.setStyle(SignInButton.SIZE_WIDE, SignInButton.COLOR_DARK, gso.getScopeArray());
// Build a GoogleApiClient with access to the Google Sign-In API and the
// options specified by gso.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
// Button listeners
findViewById(R.id.google_sign_in_button).setOnClickListener(this);
}
@Override
public void onStart() {
super.onStart();
if (getIntent().hasExtra("method")) {
if (getIntent().getStringExtra("method").equals("signOut")) {
Log.d(TAG, "Got intent to sign out");
}
} else { // Try to sign in
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result);
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
showProgressDialog();
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
@Override
public void onResult(GoogleSignInResult googleSignInResult) {
hideProgressDialog();
handleSignInResult(googleSignInResult);
}
});
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
}
}
private void AuthenticateGoogle(final String token) throws Exception {
final OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(SERVER_ADDRESS+"auth/google/idtoken?id_token="+token)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException throwable) {
Log.e(TAG, "Failed to connect to server: " + SERVER_ADDRESS + "auth/google/idtoken?id_token=" + token);
showError(R.string.server_connection_error);
throwable.printStackTrace();
}
@Override
public void onResponse(Response res) throws IOException {
if (!res.isSuccessful()) {
showError(R.string.login_no_user_error);
res.body().close();
throw new IOException("Unexpected code " + res);
} else {
Log.d(TAG, "Response code: " + res.code());
String userString = res.body().string();
System.out.println("Full response: " + userString);
String userID, userName;
try {
JSONObject user = new JSONObject(userString);
userID = user.getString("_id");
userName = user.getString("name");
Log.v(TAG, "User retrieved with ID: " + userID);
} catch (JSONException e) {
Log.e(TAG, "Unable to parse user JSON: ", e);
Log.e(TAG, "JSON String used: " + userString);
userID = null;
userName = null;
}
Log.v(TAG, "UserID: " + userID);
// Save user as loggedInUser
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString("loggedInUser", userString);
editor.putString("loggedInUserId", userID);
editor.putString("loggedInUserName", userName);
editor.commit();
startActivity(new Intent(getBaseContext(), MainActivity.class));
}
}
});
}
private void handleSignInResult(GoogleSignInResult result) {
Log.d(TAG, "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) { // Signed in successfully
GoogleSignInAccount acct = result.getSignInAccount();
String googleToken = acct.getIdToken();
Log.v(TAG, "Google token: " + googleToken);
try {
AuthenticateGoogle(acct.getIdToken());
} catch (Exception e) {
Log.e(TAG, "Error sending ID token to backend.", e);
}
} else {
Log.e(TAG, "Failed to log in: "+result.getStatus().getStatusCode());
if (result.getStatus().getStatusCode()!=4) {
showError(R.string.google_connection_error);
}
}
}
private void googleSignIn() {
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
showError(R.string.disconnected);
Log.d(TAG, "onConnectionFailed:" + connectionResult);
}
private void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getString(R.string.loading));
mProgressDialog.setIndeterminate(true);
}
mProgressDialog.show();
}
private void hideProgressDialog() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.hide();
}
}
private void showError(final int errorText) {
final TextView errorTextView = (TextView)findViewById(R.id.login_error);
runOnUiThread(new Runnable() {
@Override
public void run() {
errorTextView.setText(getText(errorText).toString());
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.google_sign_in_button:
googleSignIn();
break;
}
}
}

View File

@ -0,0 +1,182 @@
package us.keithirwin.tracman;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.DrawerLayout;
import values.AboutFragment;
import values.MainFragment;
public class MainActivity extends AppCompatActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks,
MainFragment.OnMapButtonPressedListener,
AboutFragment.OnBackButtonPressedListener {
private static final String TAG = "MainActivity";
private CharSequence mTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavigationDrawerFragment mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
// Check if gps enabled and start location service
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPref.getBoolean("gps_switch", false)) {
Log.d(TAG, "Starting LocationService");
this.startService(new Intent(this, LocationService.class));
}
}
@Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
Log.v(TAG, "onNavigationDrawerItemSelected() called");
Fragment fragment;
FragmentManager fragmentManager = getSupportFragmentManager();
switch(position) {
default:
case 0:
Log.d(TAG, "Sending intent to go to main fragment");
// Get user ID and name
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
final String mUserID = sharedPref.getString("loggedInUserId", null);
final String mUserName = sharedPref.getString("loggedInUserName", null);
if (mUserID == null) {
startActivity(new Intent(this, LoginActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
}
fragment = MainFragment.newInstance(mUserName, mUserID);
break;
case 1:
Log.v(TAG, "Sending intent to go to Settings Activity");
fragment = null;
startActivity(new Intent(this, SettingsActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
break;
case 2:
Log.v(TAG, "Sending intent to go to about fragment");
fragment = AboutFragment.newInstance();
break;
case 3:
Log.v(TAG, "Sending intent to go to logout fragment");
fragment = null;
Log.d(TAG, "Sending intent to log out");
// Stop LocationService
stopService(new Intent(this, LocationService.class));
// Send back to login screen
startActivity(new Intent(this, LoginActivity.class)
.putExtra("method", "signOut")
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
break;
}
if (fragment!=null) {
fragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commit();
}
}
public void onSectionAttached(int number) {
switch (number) {
case 0:
mTitle = getString(R.string.main_name);
// Toast.makeText(this, "main", Toast.LENGTH_SHORT).show();
break;
case 1:
// mTitle = getString(R.string.settings_name);
// Log.d(TAG, "Sending intent to go to settings");
// startActivity(new Intent(this, SettingsActivity.class)
// .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
break;
case 2:
mTitle = getString(R.string.about_name);
break;
// case 3:
// break;
}
}
public void goBack() {
onNavigationDrawerItemSelected(0);
}
public void showMap(String UserId) {
String url = "https://tracman.org/trac/id/" + UserId;
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
// public void restoreActionBar() {
// ActionBar actionBar = getActionBar();
// actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
// actionBar.setDisplayShowTitleEnabled(true);
// actionBar.setTitle(mTitle);
// }
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {}
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
((MainActivity) context).onSectionAttached(getArguments().getInt(ARG_SECTION_NUMBER));
}
}
}

View File

@ -0,0 +1,265 @@
package us.keithirwin.tracman;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
public class NavigationDrawerFragment extends Fragment {
private static final String TAG = "NavDrawerFrag";
/**
* Remember the position of the selected item.
*/
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
/**
* A pointer to the current callbacks instance (the Activity).
*/
private NavigationDrawerCallbacks mCallbacks;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout;
private ListView mDrawerListView;
private View mFragmentContainerView;
private int mCurrentSelectedPosition = 0;
// public NavigationDrawerFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
}
// Select either the default item (0) or the last selected item.
selectItem(mCurrentSelectedPosition);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Indicate that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mDrawerListView = (ListView) inflater.inflate(
R.layout.drawer, container, false);
mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
});
mDrawerListView.setAdapter(new ArrayAdapter<>(
getActionBar().getThemedContext(),
android.R.layout.simple_list_item_activated_1,
// android.R.id.text1,
new String[]{
getString(R.string.main_name),
getString(R.string.settings_name),
getString(R.string.about_name),
getString(R.string.logout_name)
}));
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return mDrawerListView;
}
// public boolean isDrawerOpen() {
// return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
// }
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* @param fragmentId The android:id of this fragment in its activity's layout.
* @param drawerLayout The DrawerLayout containing this fragment's UI.
*/
public void setUp(int fragmentId, DrawerLayout drawerLayout) {
mFragmentContainerView = getActivity().findViewById(fragmentId);
mDrawerLayout = drawerLayout;
// set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// set up the drawer's list view with items and click listener
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
mDrawerToggle = new ActionBarDrawerToggle(
getActivity(), /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
R.string.navigation_drawer_close /* "close drawer" description for accessibility */
) {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (!isAdded()) {
return;
}
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) {
return;
}
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
};
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
@Override
public void run() {
mDrawerToggle.syncState();
}
});
mDrawerLayout.addDrawerListener(mDrawerToggle);
}
private void selectItem(int position) {
Log.v(TAG, "selectItem() called");
mCurrentSelectedPosition = position;
if (mDrawerListView != null) {
Log.v(TAG, "mDrawerListView != null"); //
mDrawerListView.setItemChecked(position, true);
}
if (mDrawerLayout != null) {
Log.v(TAG, "mDrawerLayout != null"); //
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null) {
Log.v(TAG, "mCallbacks != null");
mCallbacks.onNavigationDrawerItemSelected(position);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallbacks = (NavigationDrawerCallbacks) context;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Show the global app actions in the action bar. See also
// showGlobalContextActionBar, which controls the top-left area of the action bar.
inflater.inflate(R.menu.global, menu);
showGlobalContextActionBar();
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
if (item.getItemId() == R.id.help_action) {
Toast.makeText(getActivity(), "Help is on the way! ", Toast.LENGTH_SHORT).show();
// TODO: help activity
return true;
} else if (item.getItemId() == R.id.suggestions_action) {
String url = "https://tracman.org/suggestion";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
return true;
} else if (item.getItemId() == R.id.bugs_action) {
String url = "https://tracman.org/bug?source=android";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Per the navigation drawer design guidelines, updates the action bar to show the global app
* 'context', rather than just what's in the current screen.
*/
private void showGlobalContextActionBar() {
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(R.string.app_name);
}
private ActionBar getActionBar() {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
}
/**
* Callbacks interface that all activities using this fragment must implement.
*/
public interface NavigationDrawerCallbacks {
/**
* Called when an item in the navigation drawer is selected.
*/
void onNavigationDrawerItemSelected(int position);
}
}

View File

@ -0,0 +1,232 @@
package us.keithirwin.tracman;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.MenuItem;
import java.util.List;
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p/>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends AppCompatPreferenceActivity {
private static final String TAG = "SettingsActivity";
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
Log.d(TAG, "activity onCreate called");
// Get User ID
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String mUserID = sharedPref.getString("loggedInUserId", null);
if (mUserID == null) {
startActivity(new Intent(this, LoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));
}
}
@Override
protected void onStop() {
Log.d(TAG, "onStop called");
super.onStop();
// Restart service so settings can take effect
stopService(new Intent(this, LocationService.class));
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPref.getBoolean("gps_switch", false)) {
Log.d(TAG, "Starting LocationService");
startService(new Intent(this, LocationService.class));
}
}
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()==android.R.id.home) {
// Respond to the action bar's Up/Home button
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName);
// || MapPreferenceFragment.class.getName().equals(fragmentName)
// || NotificationPreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
// Bind the summary of server address preference to its value
bindPreferenceSummaryToValue(findPreference("broadcast_frequency"));
bindPreferenceSummaryToValue(findPreference("broadcast_priority"));
}
}
/**
* This fragment shows notification preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
// @TargetApi(Build.VERSION_CODES.HONEYCOMB)
// public static class NotificationPreferenceFragment extends PreferenceFragment {
// @Override
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// addPreferencesFromResource(R.xml.pref_notification);
// setHasOptionsMenu(true);
//
// // Bind the summaries of EditText/List/Dialog/Ringtone preferences
// // to their values. When their values change, their summaries are
// // updated to reflect the new value, per the Android Design
// // guidelines.
// bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
// }
// }
/**
* This fragment shows map preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
// @TargetApi(Build.VERSION_CODES.HONEYCOMB)
// public static class MapPreferenceFragment extends PreferenceFragment {
// @Override
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// addPreferencesFromResource(R.xml.pref_map);
// setHasOptionsMenu(true);
//
// // Bind the summaries of EditText/List/Dialog/Ringtone preferences
// // to their values. When their values change, their summaries are
// // updated to reflect the new value, per the Android Design
// // guidelines.
// bindPreferenceSummaryToValue(findPreference("sync_frequency"));
// }
// }
}

View File

@ -0,0 +1,95 @@
package values;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import us.keithirwin.tracman.R;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link AboutFragment.OnBackButtonPressedListener} interface
* to handle interaction events.
* Use the {@link AboutFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class AboutFragment extends Fragment {
private OnBackButtonPressedListener mListener;
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment AboutFragment.
*/
public static AboutFragment newInstance() {
AboutFragment fragment = new AboutFragment();
return fragment;
} public AboutFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.about_name);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_about, container, false);
TextView tv = (TextView) view.findViewById(R.id.about_license);
tv.setMovementMethod(LinkMovementMethod.getInstance());
view.findViewById(R.id.about_back_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
goBack();
}
});
return view;
}
public void goBack() {
if (mListener != null) {
mListener.goBack();
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnBackButtonPressedListener) {
mListener = (OnBackButtonPressedListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>