diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1db07d5 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0f8f51a..38a4a29 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,10 @@ + + + + - + + + diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/Hyperlink.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/Hyperlink.java index 0f53e24..e43a36c 100644 --- a/app/src/main/java/dev/garrettmills/starship/hyperlink/Hyperlink.java +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/Hyperlink.java @@ -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; diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/MainActivity.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/MainActivity.java index d197727..a7f120b 100644 --- a/app/src/main/java/dev/garrettmills/starship/hyperlink/MainActivity.java +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/MainActivity.java @@ -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(); + } } } } diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/MessagingService.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/MessagingService.java new file mode 100644 index 0000000..70df2d2 --- /dev/null +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/MessagingService.java @@ -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) { + + } +} diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerRequestEndpoint.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerRequestEndpoint.java new file mode 100644 index 0000000..f44830c --- /dev/null +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerRequestEndpoint.java @@ -0,0 +1,5 @@ +package dev.garrettmills.starship.hyperlink.relay; + +public enum ServerRequestEndpoint { + LIST_THREADS +} diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerSentRequest.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerSentRequest.java new file mode 100644 index 0000000..52e9fce --- /dev/null +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/relay/ServerSentRequest.java @@ -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; + } +} diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/util/APIv1.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/APIv1.java index 41ae6d0..53d4bce 100644 --- a/app/src/main/java/dev/garrettmills/starship/hyperlink/util/APIv1.java +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/APIv1.java @@ -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()); + } } diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/util/AccessToken.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/AccessToken.java index f4a50b2..0a043e0 100644 --- a/app/src/main/java/dev/garrettmills/starship/hyperlink/util/AccessToken.java +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/AccessToken.java @@ -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; + } } diff --git a/app/src/main/java/dev/garrettmills/starship/hyperlink/util/NotAuthenticatedException.java b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/NotAuthenticatedException.java new file mode 100644 index 0000000..3c36bd7 --- /dev/null +++ b/app/src/main/java/dev/garrettmills/starship/hyperlink/util/NotAuthenticatedException.java @@ -0,0 +1,4 @@ +package dev.garrettmills.starship.hyperlink.util; + +public class NotAuthenticatedException extends Exception { +} diff --git a/app/src/main/res/layout/activity_main_status.xml b/app/src/main/res/layout/activity_main_status.xml new file mode 100644 index 0000000..092f16f --- /dev/null +++ b/app/src/main/res/layout/activity_main_status.xml @@ -0,0 +1,44 @@ + + + +