Start background service scaffolding
This commit is contained in:
parent
2e479350de
commit
a4017c303e
36
.idea/inspectionProfiles/Project_Default.xml
Normal file
36
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="TOP_LEVEL_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="INNER_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="METHOD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="FIELD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="IGNORE_DEPRECATED" value="false" />
|
||||
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
|
||||
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
|
||||
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
|
||||
<option name="myAdditionalJavadocTags" value="fixme" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -6,6 +6,10 @@
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -14,7 +18,12 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.StarshipHyperlink">
|
||||
<activity android:name=".LoginTokenFormActivity"></activity>
|
||||
<service
|
||||
android:name=".MessagingService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
|
||||
<activity android:name=".LoginTokenFormActivity" />
|
||||
<activity android:name=".LoginTokenScannerActivity" />
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
||||
import com.android.volley.RequestQueue;
|
||||
|
||||
public class Hyperlink {
|
||||
public static final int MESSAGE_TICK_POLL_INTERVAL_MS = 5000;
|
||||
public static final String SHARED_PREFERENCES_NAME = "dev.garrettmills.starship.hyperlink.main";
|
||||
public static final String SERVER_ADDR = "dev.garrettmills.starship.hyperlink.server";
|
||||
public static final String ACCESS_TOKEN = "dev.garrettmills.starship.hyperlink.token.server";
|
||||
@ -13,6 +14,10 @@ public class Hyperlink {
|
||||
|
||||
public static final int REQUEST_LOGIN_TOKEN = 180;
|
||||
public static final int REQUEST_PERMISSION_CAMERA = 181;
|
||||
public static final int REQUEST_PERMISSION_READ_SMS = 182;
|
||||
|
||||
public static final String NOTIFICATION_CHANNEL = "dev.garrettmills.starship.hyperlink.notifications";
|
||||
public static final int SERVICE_NOTIFICATION_ID = 183;
|
||||
|
||||
public static SharedPreferences preferences;
|
||||
public static RequestQueue httpRequestQueue;
|
||||
|
@ -1,22 +1,49 @@
|
||||
package dev.garrettmills.starship.hyperlink;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Telephony;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.volley.toolbox.Volley;
|
||||
|
||||
import dev.garrettmills.starship.hyperlink.util.APIv1;
|
||||
import dev.garrettmills.starship.hyperlink.util.AccessToken;
|
||||
import dev.garrettmills.starship.hyperlink.util.InvalidLoginTokenException;
|
||||
import dev.garrettmills.starship.hyperlink.util.LoginToken;
|
||||
import dev.garrettmills.starship.hyperlink.util.NotAuthenticatedException;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
TextView serverAddressView;
|
||||
Intent serviceIntent = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Hyperlink.preferences = getSharedPreferences(Hyperlink.SHARED_PREFERENCES_NAME, MODE_PRIVATE);
|
||||
Hyperlink.httpRequestQueue = Volley.newRequestQueue(this);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
NotificationChannel channel = new NotificationChannel(Hyperlink.NOTIFICATION_CHANNEL, "Starship Hyperlink", NotificationManager.IMPORTANCE_LOW);
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
assert manager != null;
|
||||
manager.createNotificationChannel(channel);
|
||||
|
||||
if (APIv1.isAuthenticated()) switchToStatusMode();
|
||||
else switchToLoginMode();
|
||||
}
|
||||
|
||||
public void onScanTokenClick(View view) {
|
||||
@ -29,6 +56,76 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivityForResult(intent, Hyperlink.REQUEST_LOGIN_TOKEN);
|
||||
}
|
||||
|
||||
public void onLogoutButtonClick(View view) {
|
||||
switchToLoginMode();
|
||||
}
|
||||
|
||||
protected void switchToStatusMode() {
|
||||
setContentView(R.layout.activity_main_status);
|
||||
serverAddressView = findViewById(R.id.activity_main_serverAddressTextView);
|
||||
|
||||
try {
|
||||
AccessToken token = APIv1.getToken();
|
||||
serverAddressView.setText(token.getServer());
|
||||
} catch (NotAuthenticatedException e) {
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
Toast.makeText(getApplicationContext(), "Cannot fetch token; not authenticated.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
APIv1.logout();
|
||||
switchToLoginMode();
|
||||
return;
|
||||
}
|
||||
|
||||
requestSMSRead();
|
||||
Cursor cursor = getContentResolver().query(Telephony.Threads.CONTENT_URI, null, null, null, "date DESC");
|
||||
if ( cursor.moveToFirst() ) {
|
||||
int j = 0;
|
||||
do {
|
||||
j += 1;
|
||||
if ( j > 5 ) break;
|
||||
for ( int i = 0; i < cursor.getColumnCount(); i += 1 ) {
|
||||
Log.i("MainActivity", cursor.getColumnName(i) + ": " + cursor.getString(i));
|
||||
}
|
||||
} while ( cursor.moveToNext() );
|
||||
}
|
||||
|
||||
serviceIntent = new Intent(this, MessagingService.class);
|
||||
ContextCompat.startForegroundService(this, serviceIntent);
|
||||
}
|
||||
|
||||
protected void switchToLoginMode() {
|
||||
APIv1.logout();
|
||||
|
||||
if ( serviceIntent != null ) stopService(serviceIntent);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
serverAddressView = null;
|
||||
}
|
||||
|
||||
protected void requestSMSRead() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED) {
|
||||
// switchToStatusMode();
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_SMS}, Hyperlink.REQUEST_PERMISSION_READ_SMS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == Hyperlink.REQUEST_PERMISSION_READ_SMS) {
|
||||
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
switchToStatusMode();
|
||||
} else {
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
Toast.makeText(this, "Read SMS permission denied", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
switchToLoginMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
@ -38,6 +135,15 @@ public class MainActivity extends AppCompatActivity {
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
Toast.makeText(getApplicationContext(), data.getExtras().getString(Hyperlink.EXTRA_LOGIN_TOKEN), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
try {
|
||||
LoginToken login = new LoginToken(data.getExtras().getString(Hyperlink.EXTRA_LOGIN_TOKEN));
|
||||
APIv1.login(login);
|
||||
switchToStatusMode();
|
||||
} catch (InvalidLoginTokenException e) {
|
||||
Toast.makeText(getApplicationContext(), "Invalid login token!", Toast.LENGTH_SHORT).show();
|
||||
switchToLoginMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
package dev.garrettmills.starship.hyperlink;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.sql.Time;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import dev.garrettmills.starship.hyperlink.relay.ServerSentRequest;
|
||||
|
||||
public class MessagingService extends Service {
|
||||
private final Handler handler = new Handler();
|
||||
private final Timer timer = new Timer();
|
||||
|
||||
public MessagingService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Intent notificationIntent = new Intent(this, MainActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
|
||||
|
||||
Notification notification = new Notification.Builder(this, Hyperlink.NOTIFICATION_CHANNEL)
|
||||
.setContentTitle("Starship Hyperlink is Running")
|
||||
.setContentText("Your text messages are being relayed to Hyperlink.")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setTicker("ticker")
|
||||
.build();
|
||||
|
||||
startForeground(Hyperlink.SERVICE_NOTIFICATION_ID, notification);
|
||||
startConnection();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
private void startConnection() {
|
||||
TimerTask task = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.post(() -> {
|
||||
if ( !tick() ) {
|
||||
stopSelf();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
timer.schedule(task, 0, Hyperlink.MESSAGE_TICK_POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private boolean tick() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void handleServerSentRequest(ServerSentRequest request) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package dev.garrettmills.starship.hyperlink.relay;
|
||||
|
||||
public enum ServerRequestEndpoint {
|
||||
LIST_THREADS
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.garrettmills.starship.hyperlink.relay;
|
||||
|
||||
public class ServerSentRequest {
|
||||
private String _uuid;
|
||||
private ServerRequestEndpoint _endpoint;
|
||||
|
||||
public String getUUID() {
|
||||
return _uuid;
|
||||
}
|
||||
|
||||
public ServerRequestEndpoint getEndpoint() {
|
||||
return _endpoint;
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package dev.garrettmills.starship.hyperlink.util;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import dev.garrettmills.starship.hyperlink.Hyperlink;
|
||||
|
||||
public class APIv1 {
|
||||
@ -22,4 +24,45 @@ public class APIv1 {
|
||||
|
||||
return server + "/api/v1" + endpoint;
|
||||
}
|
||||
|
||||
public static boolean isAuthenticated() {
|
||||
return !Hyperlink.preferences.getString(Hyperlink.SERVER_ADDR, "").equals("")
|
||||
&& !Hyperlink.preferences.getString(Hyperlink.ACCESS_TOKEN, "").equals("");
|
||||
}
|
||||
|
||||
public static AccessToken getToken() throws NotAuthenticatedException {
|
||||
if ( !isAuthenticated() ) {
|
||||
throw new NotAuthenticatedException();
|
||||
}
|
||||
|
||||
return new AccessToken(
|
||||
Hyperlink.preferences.getString(Hyperlink.SERVER_ADDR, ""),
|
||||
Hyperlink.preferences.getString(Hyperlink.ACCESS_TOKEN, "")
|
||||
);
|
||||
}
|
||||
|
||||
public static void logout() {
|
||||
SharedPreferences.Editor editor = Hyperlink.preferences.edit();
|
||||
editor.putString(Hyperlink.SERVER_ADDR, "");
|
||||
editor.putString(Hyperlink.ACCESS_TOKEN, "");
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static AccessToken login(LoginToken login) {
|
||||
AccessToken token = redeemLoginToken(login);
|
||||
SharedPreferences.Editor editor = Hyperlink.preferences.edit();
|
||||
editor.putString(Hyperlink.SERVER_ADDR, token.getServer());
|
||||
editor.putString(Hyperlink.ACCESS_TOKEN, token.getToken());
|
||||
editor.apply();
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fixme this is a stub placeholder. Replace with actual implementation
|
||||
* @param login the LoginToken from the user
|
||||
* @return the AccessToken redeemed from the server
|
||||
*/
|
||||
protected static AccessToken redeemLoginToken(LoginToken login) {
|
||||
return new AccessToken(login.getServer(), login.getToken());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,21 @@
|
||||
package dev.garrettmills.starship.hyperlink.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class AccessToken {
|
||||
private final String _server;
|
||||
private final String _token;
|
||||
|
||||
public AccessToken(@NonNull String server, @NonNull String token) {
|
||||
_server = server;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return _server;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return _token;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
package dev.garrettmills.starship.hyperlink.util;
|
||||
|
||||
public class NotAuthenticatedException extends Exception {
|
||||
}
|
44
app/src/main/res/layout/activity_main_status.xml
Normal file
44
app/src/main/res/layout/activity_main_status.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/activity_main_logoutButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="onLogoutButtonClick"
|
||||
android:text="Log Out"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.498"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.606" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="140dp"
|
||||
android:text="Connected to Hyperlink"
|
||||
android:textColor="#4CAF50"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/activity_main_logoutButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/activity_main_serverAddressTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/activity_main_logoutButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user