Search code examples
androidjavafxalarmmanagergluongluon-mobile

Schedule a Task in Android using ALARM_SERVICE service in Gluon-mobile


I have referred from this link and tried this code So far.But I don't know to implement this code in gluon-mobile.What I intent to do is that I want to start an Indent that does some Job in background at a specific time.My problem is that I don't how gluon-mobile behaves.So help me complete it.

package com.application;

import java.util.Calendar;

import android.app.AlarmManager; 
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import javafxports.android.FXActivity;

 public class Schedular {

AlarmManager manager=null;
FXActivity activity=null;
  public Schedular()
{
 manager=(AlarmManager) FXActivity.getInstance().getSystemService(Context.ALARM_SERVICE);
  activity=FXActivity.getInstance();
}

public void schedule()
{

    Intent indent = new Intent(activity,Alarm.class);
    PendingIntent pIntent = PendingIntent.getBroadcast(activity, 0, indent, 0);

manager.set(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime()+2*1000,indent);  //This is Where I went Wrong

}
}

I want to schedule a task at specific timing given by the user at the Run-time,That's what the function public void schedule(Calendar calendar,boolean flag) does.

Alarm.class

 public class Alarm extends BroadcastReceiver
 {

 @Override
 public void onReceive(Context context, Intent intent)
 {

     // Your code to execute when the alarm triggers
     // and the broadcast is received.   
    new bluetooth.turnOn();  
 }

And I have added Alarm.class to AndroidManifest.xml as

 <?xml version="1.0" encoding="UTF-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.application" android:versionCode="1" android:versionName="1.0">
    <supports-screens android:xlargeScreens="true"/>

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="26"/>


    <application android:label="Wifischeduler" android:name="android.support.multidex.MultiDexApplication" android:icon="@mipmap/ic_launcher">

            <activity android:name="javafxports.android.FXActivity" android:label="Wifischeduler" android:configChanges="orientation|screenSize">
                    <meta-data android:name="main.class" android:value="com.application.Main"/>
                    <meta-data android:name="debug.port" android:value="0"/>
                    <intent-filter>
                            <action android:name="android.intent.action.MAIN"/>
                            <category android:name="android.intent.category.LAUNCHER"/>
                    </intent-filter>
            </activity>

        <activity android:name="com.gluonhq.impl.charm.down.plugins.android.NotificationActivity"
                       android:parentActivityName="javafxports.android.FXActivity">
           <meta-data android:name="android.support.PARENT_ACTIVITY" 
                 android:value="javafxports.android.FXActivity"/>
     </activity>

 <activity android:name="com.application.AndroidPlatform$PermissionRequestActivity" />

<receiver android:name="com.application.Alarm"  />  // this is where I have included the Alarm.class

<receiver android:name="com.gluonhq.impl.charm.down.plugins.android.AlarmReceiver" />


    </application>

When the schedule method is called the app exits... I have run adb logcat -v threadtime and got this output on cmd

--------- beginning of crash
07-22 09:21:44.878 13516 13516 E AndroidRuntime: FATAL EXCEPTION: main
07-22 09:21:44.878 13516 13516 E AndroidRuntime: Process:    com.application, PID: 13516
07-22 09:21:44.878 13516 13516 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate receiver com.application.Alarm: java.lang.IllegalAccessException: java.lang.Class<com.application.Alarm> is not accessible from java.lang.Class<android.app.ActivityThread>
 07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2739)
 07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.app.ActivityThread.access$1900(ActivityThread.java:153)
 07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1452)
 07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:102)
 07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:154)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:5529)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
07-22 09:21:44.878 13516 13516 E AndroidRuntime: Caused by: java.lang.IllegalAccessException: java.lang.Class<com.application.Alarm> is not accessible from java.lang.Class<android.app.ActivityThread>
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at java.lang.Class.newInstance(Native Method)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        at android.app.ActivityThread.handleReceiver(ActivityThread.java:2734)
07-22 09:21:44.878 13516 13516 E AndroidRuntime:        ... 8 more
07-22 09:21:44.885  1804  3742 D StatusBar: onNotificationPosted:  Key: 0|com.application|123456|charm://down/Id/abcd12340|10276 GroupKey: 0|com.application|123456|charm://down/Id/abcd12340|10276 Connected: true
07-22 09:21:44.885  1804  1804 D StatusBar:  GroupChild: false  GroupsContainsKey: false  IsUpdate: false IsGroupSummary: false hasIcon: true
07-22 09:21:44.886  1804  1804 D PhoneStatusBar: addNotification pkg=com.application;basepkg=com.application;id=123456
07-22 09:21:44.888  3047 10359 I GCoreUlr: Successfully inserted 1 locations
07-22 09:21:44.922  1804  1804 W ProgressBarDelegate: Unknown Drawable subclass, src=android.graphics.drawable.ScaleDrawable@eb9196
07-22 09:21:44.922  1804  1804 W ProgressBarDelegate: Unknown Drawable subclass, src=android.graphics.drawable.ScaleDrawable@aace117
07-22 09:21:44.932  3451  3518 I octvm_klo: klo lock

I have no idea why I can't Instantiate a Receiver (Alarm.class),

@FXML void start()
{

    try{

    Schedule schedule =(Schedule) Class.forName("com.application.Schedular").newInstance();


    schedule.schedule();

    }

Solution

  • Your code didn't exactly work for me: I had to change the intent to the pIntent in schedule(), and I changed Schedule to Class on the Java implementation (how do you import it anyway?). Other than that, it was running fine for me (using Android target 25), and I didn't have any exception.

    I'll post the initial solution (based on your approach), and after that a better way to do it.

    Initial solution

    While the preferred way to add platform specific code is following Charm Down design pattern (see https://bitbucket.org/gluon-oss/charm-down), a very quick and dirty way of getting the Android code working is this:

    Android

    Scheduler.java

    package com.gluonhq.scheduler.android;
    
    import android.app.AlarmManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.os.SystemClock;
    import javafxports.android.FXActivity;
    
    public class Scheduler {
    
        private final AlarmManager manager;
    
        public Scheduler() {
            manager = (AlarmManager) FXActivity.getInstance().getSystemService(Context.ALARM_SERVICE);
            Intent indent = new Intent(FXActivity.getInstance(), AlarmReceiver.class);
            PendingIntent pIntent = PendingIntent.getBroadcast(FXActivity.getInstance(), 0, indent, 0);
    
            manager.set(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime() + 2000, pIntent); 
        }
    }
    

    AlarmReceiver.java

    package com.gluonhq.scheduler.android;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.Toast;
    
    public class AlarmReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "Alarm ON", Toast.LENGTH_LONG).show();
        }
    
    }
    

    AndroidManifest.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.gluonhq.scheduler" android:versionCode="1" android:versionName="1.0">
            <supports-screens android:xlargeScreens="true"/>
            <uses-permission android:name="android.permission.INTERNET"/>
            <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
            <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="25"/>
            <application android:label="GluonScheduler" android:name="android.support.multidex.MultiDexApplication" android:icon="@mipmap/ic_launcher">
                    <activity android:name="javafxports.android.FXActivity" android:label="GluonScheduler" android:configChanges="orientation|screenSize">
                            <meta-data android:name="main.class" android:value="com.gluonhq.scheduler.GluonScheduler"/>
                            <meta-data android:name="debug.port" android:value="0"/>
                            <intent-filter>
                                    <action android:name="android.intent.action.MAIN"/>
                                    <category android:name="android.intent.category.LAUNCHER"/>
                            </intent-filter>
                    </activity>
                    <receiver android:name="com.gluonhq.scheduler.android.AlarmReceiver" />
            </application>
    </manifest
    

    Main/Java

    This is a Basic View from a Single view template project using the Gluon IDE plugin:

    GluonScheduler.java

    package com.gluonhq.scheduler;
    
    import com.gluonhq.charm.glisten.application.MobileApplication;
    import com.gluonhq.charm.glisten.visual.Swatch;
    import javafx.scene.Scene;
    import javafx.scene.image.Image;
    import javafx.stage.Stage;
    
    public class GluonScheduler extends MobileApplication {
    
        public static final String BASIC_VIEW = HOME_VIEW;
    
        @Override
        public void init() {
            addViewFactory(BASIC_VIEW, () -> new BasicView(BASIC_VIEW));
        }
    
        @Override
        public void postInit(Scene scene) {
            Swatch.BLUE.assignTo(scene);
    
            ((Stage) scene.getWindow()).getIcons().add(new Image(GluonScheduler.class.getResourceAsStream("/icon.png")));
        }
    }
    

    BasicView.java

    package com.gluonhq.scheduler;
    
    import com.gluonhq.charm.glisten.control.AppBar;
    import com.gluonhq.charm.glisten.control.Icon;
    import com.gluonhq.charm.glisten.mvc.View;
    import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javafx.geometry.Pos;
    import javafx.scene.control.Button;
    import javafx.scene.layout.VBox;
    
    public class BasicView extends View {
    
        public BasicView(String name) {
            super(name);
    
            Button button = new Button("Schedule task");
            button.setGraphic(new Icon(MaterialDesignIcon.ALARM_ON));
            button.setOnAction(e -> {
    
                try {
                    Class c = Class.forName("com.gluonhq.scheduler.android.Scheduler");
                    c.newInstance();
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
                    Logger.getLogger(BasicView.class.getName()).log(Level.SEVERE, null, ex);
                }
    
            });
    
            VBox controls = new VBox(button);
            controls.setAlignment(Pos.CENTER);
    
            setCenter(controls);
        }
    
        @Override
        protected void updateAppBar(AppBar appBar) {
            appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
            appBar.setTitleText("Basic View");
        }
    }
    

    build.gradle

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'org.javafxports:jfxmobile-plugin:1.3.6'
        }
    }
    
    apply plugin: 'org.javafxports.jfxmobile'
    
    repositories {
        jcenter()
        maven {
            url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
        }
    }
    
    mainClassName = 'com.gluonhq.scheduler.GluonScheduler'
    
    dependencies {
        compile 'com.gluonhq:charm:4.3.5'
    }
    
    jfxmobile {
        downConfig {
            version = '3.3.0'
            // Do not edit the line below. Use Gluon Mobile Settings in your project context menu instead
            plugins 'display', 'lifecycle', 'statusbar', 'storage'
        }
        android {
            manifest = 'src/android/AndroidManifest.xml'
        }
        ios {
            infoPList = file('src/ios/Default-Info.plist')
            forceLinkClasses = [
                    'com.gluonhq.**.*',
                    'javax.annotations.**.*',
                    'javax.inject.**.*',
                    'javax.json.**.*',
                    'org.glassfish.json.**.*'
            ]
        }
    }
    

    Deploy to your Android device (./gradlew androidInstall) and run. Press the button and two seconds later you will see the toast:

    Preferred solution

    AlarmService

    While the above code works, it is not very convenient to use Class.forName.

    Following the Charm Down services (LocalNotification for instance), I'd do it this way instead (you have to keep the same package names):

    Java

    AlarmService.java

    package com.gluonhq.charm.down.plugins;
    
    public interface AlarmService {
        public void schedule();
    }
    

    AlarmServiceFactory.java

    package com.gluonhq.charm.down.plugins;
    
    import com.gluonhq.charm.down.DefaultServiceFactory;
    
    public class AlarmServiceFactory extends DefaultServiceFactory<AlarmService> {
    
        public AlarmServiceFactory() {
            super(AlarmService.class);
        }
    
    }
    

    Android

    AndroidAlarmService.java

    package com.gluonhq.charm.down.plugins.android;
    
    import android.app.AlarmManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.os.SystemClock;
    import com.gluonhq.charm.down.plugins.AlarmService;
    import com.gluonhq.scheduler.android.AlarmReceiver;
    import javafxports.android.FXActivity;
    
    public class AndroidAlarmService implements AlarmService {
    
        private final AlarmManager manager;
        private final FXActivity activity;
    
        public AndroidAlarmService() {
            activity = FXActivity.getInstance();
            manager = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
        }
        @Override
        public void schedule() {
            Intent indent = new Intent(activity, AlarmReceiver.class);
            PendingIntent pIntent = PendingIntent.getBroadcast(activity, 0, indent, 0);
    
            manager.set(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime() + 2000, pIntent); 
        }
    
    }
    

    (keeping the same AlarmReceiver class as above).

    Finally, on your view:

    BasicView.java

    button.setOnAction(e -> 
            Services.get(AlarmService.class).ifPresent(AlarmService::schedule));
    

    Now you can deploy and run again, and the result will be the same. But obviously this preferred way has many more advantages.

    • Using the service you can also run on desktop or deploy to iOS: the app will work (when clicking the button it won't do anything).
    • You can implement solutions for desktop and iOS if you need to.
    • You can easily add more functionality through the interface.