From e11b958c33aedd8ea30511b25e57af7d2136bbab Mon Sep 17 00:00:00 2001 From: birudo Date: Sat, 17 May 2025 16:39:55 +0800 Subject: [PATCH 1/4] start server when onStart --- .../com/kamwithk/ankiconnectandroid/MainActivity.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java b/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java index ca068b3..475f33f 100644 --- a/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java +++ b/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java @@ -83,6 +83,11 @@ protected void onCreate(Bundle savedInstanceState) { }); } + protected void onStart() { + super.onStart(); + startServiceWrap(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar_menu, menu); @@ -143,6 +148,10 @@ public void startService() { public void startServiceBtn(View view) { + startServiceWrap(); + } + + public void startServiceWrap(){ boolean notificationsEnabled = notificationManager.areNotificationsEnabled(); if (notificationsEnabled) { startService(); From a2b9002b4495205df55b176a16b0dc47091bd2c6 Mon Sep 17 00:00:00 2001 From: birudo Date: Fri, 23 May 2025 22:50:07 +0800 Subject: [PATCH 2/4] feat:add state to buttons --- .vscode/settings.json | 3 + app/build.gradle | 5 +- .../ankiconnectandroid/MainActivity.java | 71 +++++++++++++++---- .../kamwithk/ankiconnectandroid/Service.java | 14 +++- app/src/main/res/layout/activity_main.xml | 2 + 5 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..92d4b36 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.jdt.ls.androidSupport.enabled": "on" +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index b90b26b..5c74a47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,8 +37,9 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_9 - targetCompatibility JavaVersion.VERSION_1_9 + // Required JDK version: JDK 11 (or newer, but JDK 11 is the baseline for AGP 7.4). + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } } diff --git a/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java b/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java index 475f33f..70d5026 100644 --- a/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java +++ b/app/src/main/java/com/kamwithk/ankiconnectandroid/MainActivity.java @@ -13,6 +13,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.Toast; +import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; import androidx.activity.result.ActivityResultLauncher; @@ -22,9 +23,10 @@ import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.Observer; -import com.kamwithk.ankiconnectandroid.ankidroid_api.IntegratedAPI; +import com.kamwithk.ankiconnectandroid.ankidroid_api.IntegratedAPI; public class MainActivity extends AppCompatActivity { @@ -56,6 +58,8 @@ public void onClick(DialogInterface dialog, int id) { public static final String CHANNEL_ID = "ankiConnectAndroid"; private NotificationManager notificationManager; private ActivityResultLauncher requestPermissionLauncher; + private Button startServiceButton; + private Button stopServiceButton; @Override protected void onCreate(Bundle savedInstanceState) { @@ -64,26 +68,45 @@ protected void onCreate(Bundle savedInstanceState) { // toolbar support Toolbar toolbar = findViewById(R.id.materialToolbar); + + try { + startServiceButton = findViewById(R.id.start_service_btn); + stopServiceButton = findViewById(R.id.stop_service_btn); + } catch (Exception e) { + android.util.Log.e("MainActivity", "Could not find start/stop buttons. Check IDs in XML.", e); + } + setSupportActionBar(toolbar); IntegratedAPI.authenticate(this); - NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "Ankiconnect Android", NotificationManager.IMPORTANCE_DEFAULT); + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "Ankiconnect Android", + NotificationManager.IMPORTANCE_DEFAULT); notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(notificationChannel); - // this cannot be put inside attemptGrantNotifyPermissions, because it is called by + // this cannot be put inside attemptGrantNotifyPermissions, because it is called + // by // a onClickListener and crashes the app: https://stackoverflow.com/a/67582633 - requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), + isGranted -> { if (!isGranted) { - Toast.makeText(this, "Attempting to start server without notification...", Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Attempting to start server without notification...", Toast.LENGTH_LONG) + .show(); } startService(); }); + + // Observe the service running state + Service.serviceRunningState.observe(this, new Observer() { + @Override + public void onChanged(Boolean isRunning) { + updateButtonStates(isRunning); + } + }); } - protected void onStart() { + protected void onStart() { super.onStart(); startServiceWrap(); } @@ -122,13 +145,17 @@ public void attemptGrantNotificationPermissions() { // explanation for shouldShowRequestPermissionRationale is shown below // (taken from: https://stackoverflow.com/a/39739972): // - // This method returns true if the app has requested this permission previously and the - // user denied the request. Note: If the user turned down the permission request in the - // past and chose the Don't ask again option in the permission request system dialog, + // This method returns true if the app has requested this permission previously + // and the + // user denied the request. Note: If the user turned down the permission request + // in the + // past and chose the Don't ask again option in the permission request system + // dialog, // this method returns false. if (shouldShowRequestPermissionRationale(POST_NOTIFICATIONS)) { // Explain that notifications are "needed" to display the server - new NotificationsPermissionDialogFragment().show(this.getSupportFragmentManager(), "post_notifications_dialog"); + new NotificationsPermissionDialogFragment().show(this.getSupportFragmentManager(), + "post_notifications_dialog"); } else { // Directly ask for the permission. requestPermissionLauncher.launch(POST_NOTIFICATIONS); @@ -146,12 +173,11 @@ public void startService() { ContextCompat.startForegroundService(this, serviceIntent); } - public void startServiceBtn(View view) { startServiceWrap(); } - public void startServiceWrap(){ + public void startServiceWrap() { boolean notificationsEnabled = notificationManager.areNotificationsEnabled(); if (notificationsEnabled) { startService(); @@ -164,4 +190,23 @@ public void stopServiceBtn(View view) { Intent serviceIntent = new Intent(this, Service.class); stopService(serviceIntent); } + + private void updateButtonStates(boolean isRunning) { + if (startServiceButton == null || stopServiceButton == null) { + return; + } + + if (isRunning) { + startServiceButton.setEnabled(false); + stopServiceButton.setEnabled(true); + startServiceButton.setText("Server is running at port " + Service.PORT); + stopServiceButton.setText("Stop Service"); + return; + } + + startServiceButton.setEnabled(true); + stopServiceButton.setEnabled(false); + startServiceButton.setText("Start Service"); + stopServiceButton.setText("Server stopped"); + } } diff --git a/app/src/main/java/com/kamwithk/ankiconnectandroid/Service.java b/app/src/main/java/com/kamwithk/ankiconnectandroid/Service.java index 7ed4994..93b9843 100644 --- a/app/src/main/java/com/kamwithk/ankiconnectandroid/Service.java +++ b/app/src/main/java/com/kamwithk/ankiconnectandroid/Service.java @@ -8,6 +8,7 @@ import android.util.Log; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import androidx.lifecycle.MutableLiveData; import com.kamwithk.ankiconnectandroid.routing.Router; import java.io.IOException; @@ -19,6 +20,9 @@ public class Service extends android.app.Service { private Router server; + // LiveData to observe service state + public static final MutableLiveData serviceRunningState = new MutableLiveData<>(false); + @Override public void onCreate() { // Only one time super.onCreate(); @@ -51,15 +55,21 @@ public int onStartCommand(Intent intent, int flags, int startId) { // Every time startForeground(1, notification); + // Update LiveData on main thread + serviceRunningState.postValue(true); return START_STICKY; } @Override public void onDestroy() { - server.stop(); + if (server != null) { + server.stop(); + } + + // Update LiveData on main thread + serviceRunningState.postValue(false); super.onDestroy(); } - @Nullable @Override public IBinder onBind(Intent intent) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cd481d0..0396c4b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -47,12 +47,14 @@ app:layout_constraintTop_toTopOf="parent" />