Melanjutkan pembahasan tentang perizinan pada artikel sebelumnya, seperti sudah dijelaskan mengenai mulai otomatis (auto start), berikut ini saya akan membuat aplikasi yang memanfaatkan mulai otomatis, di mana aplikasi akan menjalankan sebuah service saat perangkat dinyalakan ulang (reboot). Service dari aplikasi ini akan menampilkan pesan notifikasi setiap jam.
Kita mulai dengan membuat sebuah proyek baru dengan Basic Activity sebagai pilihan pola main activity. Atur proyek Anda seperti di bawah ini.
- Name: Hourly Notification
- Package: com.inochi.notification
- Language: Java
Saya akan menambahkan beberapa kelas baru pada proyek ini. Agar lebih memudahkan dalam pengelompokkan kelas, saya menambahkan beberapa paket baru pada proyek di antaranya: helper, item, service, dan util.
Untuk menambahkan paket baru, lakukan langkah berikut ini. Pada panel Project, perluas cabang app, kemudian java, kemudian nama paket (com.inochi.notification) dengan mengklik ganda pada nama cabang tersebut.
Selanjutnya klik kanan pada cabang nama paket (com.inochi.notification), sorot menu New, kemudian klik Package. Pada dialog New Package beri nama paket baru yaitu helper. Lakukan cara ini kembali untuk membuat paket-paket baru lainnya, yaitu: item, service, dan util.
Kita akan menambahkan beberapa kelas pada paket helper. Klik kanan pada cabang helper, sorot New, kemudian klik Java Class. Kelas pertama yang akan kita buat bernama Constants.
Lengkapi kode sumber untuk kelas Constants menjadi seperti di bawah ini.
package com.inochi.notification.helper; public final class Constants { public static class Default { public static final String APP = "com.inochi.notification"; private static final int REQUEST = 2000; } public static final class Action { public static final String CLOSE_NOTIFY = Default.APP + ".action." + "CLOSE_NOTIFY"; public static final String SHOW_NOTIFY = Default.APP + ".action." + "SHOW_NOTIFY"; public static final String CREATE_DAILY = Default.APP + ".action." + "CREATE_DAILY"; } public static final class Setting { public static final String BOOT_PERMISSION = Default.APP + ".setting." + "BOOT_PERMISSION"; public static final String NOTIF_ID = Default.APP + ".setting." + "NOTIF_ID"; public static final String NOTIF_TITLE = Default.APP + ".setting." + "NOTIF_TITLE"; public static final String NOTIF_TEXT = Default.APP + ".setting." + "NOTIF_TEXT"; public static final String NOTIF_ICON = Default.APP + ".setting." + "NOTIF_ICON"; } public static final class Permission { public static final class Type { public static final int RECEIVE_BOOT_COMPLETED = Default.REQUEST + 1; public static final int WAKE_LOCK = Default.REQUEST + 2; public static final int VIBRATE = Default.REQUEST + 3; } } }
Masih pada paket helper, tambahkan kelas baru dengan nama Settings kemudian lengkapi kode sumbernya menjadi seperti di bawah ini.
package com.inochi.notification.helper; import android.content.Context; import android.content.SharedPreferences; public class Settings { private SharedPreferences settings; private Context context; private String name; public Settings(Context context, String name){ this.context = context; this.name = name; settings = context.getSharedPreferences(name, Context.MODE_PRIVATE); } public Settings(Context context){ this(context, context.getPackageName()); } public void setSetting(String key, String value){ SharedPreferences.Editor editor = settings.edit(); editor.putString(key, value); editor.apply(); } public void deleteSetting(){ settings = context.getSharedPreferences(name, Context.MODE_PRIVATE); settings.edit().clear().apply(); } public String getSetting(String key, String defVal){ return settings.getString(key, defVal); } public String getSetting(String key){ return settings.getString(key, ""); } public int getIntSetting(String strKey, int defVal){ String strValue = String.valueOf(defVal); String strSetting = getSetting(strKey, strValue); return Integer.parseInt(strSetting); } public int getIntSetting(String strKey){ String strSetting = getSetting(strKey); return Integer.parseInt(strSetting); } public void setIntSetting(String strKey, int defVal){ String strValue = String.valueOf(defVal); setSetting(strKey, strValue); } public short getShortSetting(String strKey, short defVal){ String strValue = String.valueOf(defVal); String strSetting = getSetting(strKey, strValue); return Short.parseShort(strSetting); } public void setShortSetting(String strKey, short defVal){ String strValue = String.valueOf(defVal); setSetting(strKey, strValue); } public long getLongSetting(String strKey, long defVal){ String strValue = String.valueOf(defVal); String strSetting = getSetting(strKey, strValue); return Long.parseLong(strSetting); } public long getLongSetting(String strKey){ String strSetting = getSetting(strKey); return Long.parseLong(strSetting); } public void setLongSetting(String strKey, long defVal){ String strValue = String.valueOf(defVal); setSetting(strKey, strValue); } }
Dan masih pada paket helper, tambahkan sebuah kelas baru lagi dengan nama BundleSettings, dan lengkapi kode sumbernya seperti di bawah ini.
package com.inochi.notification.helper; import android.content.Context; public class BundleSettings { private Context context; private Settings settings; public BundleSettings(Context context){ this.context = context; this.settings = new Settings(context); } public int getBootPremission(){ return settings.getIntSetting(Constants.Setting.BOOT_PERMISSION, 0); } public void setBootPremission(int value){ settings.setIntSetting(Constants.Setting.BOOT_PERMISSION, value); } }
Kegunaan kelas Constants, Settings, dan BundleSettings sama persis seperti dijelaskan pada artikel sebelumnya.
Berikutnya kita akan menambahkan sebuah kelas baru pada paket item, dengan nama NotifItem. Kelas ini digunakan sebagai obyek untuk menampung informasi atau properti dari notifikasi yang nantinya akan ditampilkan. Lengkapi kode sumbernya menjadi seperti di bawah ini.
package com.inochi.notification.item; import java.io.Serializable; public class NotifItem implements Serializable { private int id; private String ticker; private String title; private String message; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getTicker() { return ticker; } public void setTicker(String ticker) { this.ticker = ticker; } }
Selanjutnya kita akan menambahkan sebuah komponen notifikasi (UI Notification) pada paket util. Klik kanan cabang util, sorot menu New, sorot menu UI Component, kemudian klik menu Notification.
Pada dialog wisaya yang ditampilkan, isi Class Name dengan TestNotification. Ubah Style when Expanded menjadi None, klik tombol Next untuk melanjutkan ke wisaya berikutnya.
Tidak perlu melakukan apapun pada dialog wisaya berikutnya (Generate Icon), akhiri saja dengan mengklik tombol Finish.
Ubah kode sumber kelas TestNotification menjadi seperti di bawah ini.
package com.inochi.notification.util; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.support.v4.app.NotificationCompat; import com.inochi.notification.R; import com.inochi.notification.helper.Constants; import com.inochi.notification.item.NotifItem; import com.inochi.notification.service.TestReceiver; public class TestNotification { public static void notify(final Context context, NotifItem notifItem) { final Resources res = context.getResources(); final Bitmap picture = BitmapFactory.decodeResource(res, R.drawable.example_picture); final int id = notifItem.getId(); final String ticker = notifItem.getTicker(); final String title = notifItem.getTitle(); final String text = notifItem.getMessage(); Intent intentReceiver = new Intent(context, TestReceiver.class); intentReceiver.putExtra(Constants.Setting.NOTIF_ID, id); intentReceiver.setAction(Constants.Action.CLOSE_NOTIFY); PendingIntent closeNotifyIntent = PendingIntent.getBroadcast(context, id, intentReceiver, PendingIntent.FLAG_UPDATE_CURRENT); long[] pattern = null; NotificationManager notificationManager = createNotificationChannel(context); final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Constants.Default.APP) .setDefaults(NotificationCompat.FLAG_AUTO_CANCEL) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(R.drawable.ic_stat_test) .setContentTitle(title) .setContentText(text) .setLargeIcon(picture) .setTicker(ticker) .setVibrate(pattern) .setStyle(new NotificationCompat.BigTextStyle().bigText(text)) .setAutoCancel(true) .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Close", closeNotifyIntent) ; notificationManager.notify(id, builder.build()); } private static NotificationManager createNotificationChannel(Context context) { NotificationManager notificationManager; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { notificationManager = context.getSystemService(NotificationManager.class); } else { notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = context.getString(R.string.app_name); String description = context.getString(R.string.app_name); int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(Constants.Default.APP, name, importance); channel.setDescription(description); notificationManager.createNotificationChannel(channel); } return notificationManager; } public static void cancel(final Context context, int id) { final NotificationManager nm = createNotificationChannel(context); nm.cancel(id); } }
Abaikan dulu jika terdapat kesalahan pada kode sumber kelas TestNotification, karena berhubungan dengan proses berikutnya. Kelas TestNotification digunakan sebagai UI dari notifikasi yang akan ditampilkan oleh aplikasi.
Berikutnya kita akan menambahkan dua buah kelas pada paket service. Klik kanan pada cabang service, sorot menu New, sorot menu Other, kemudian klik Broadcast Receiver. Cukup isikan Class Name dengan TestReceiver kemudian klik tombol Finish.
Ubah kode sumber kelas TestReceiver menjadi seperti di bawah ini.
package com.inochi.notification.service; import android.content.Context; import android.content.Intent; import android.support.v4.content.WakefulBroadcastReceiver; public class TestReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) action = ""; if (!action.isEmpty()){ Intent service = new Intent(context, MainService.class); service.setAction(action); service.putExtras(intent); startWakefulService(context, service); } } }
Abaikan juga jika terdapat kesalahan pada kode sumber TestReceiver. Kelas ini digunakan untuk menangkap kiriman perintah brodcast dari komponen aplikasi yang kemudian akan diteruskan ke sebuah service.
Selanjutnya kita akan menambahkan kelas kedua pada paket service. Klik kanan pada cabang service, sorot menu New, sorot menu Service, kemudian klik menu Service. Isi Class Name dengan MainService, kemudian akhiri wisaya dengan mengklik tombol Finish.
Ubah kode sumber kelas MainService menjadi seperti di bawah ini.
package com.inochi.notification.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import com.inochi.notification.helper.Constants; import com.inochi.notification.item.NotifItem; import com.inochi.notification.util.TestNotification; import java.util.Calendar; public class MainService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } public MainService() { super(); mName = "MainService"; } public void setIntentRedelivery(boolean enabled) { mRedelivery = enabled; } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_STICKY; } @Override public void onDestroy() { mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; } public void onHandleIntent(Intent intent){ String action = intent.getAction(); if (action == null) action = ""; if (!action.isEmpty()){ Bundle args = intent.getExtras(); assert args != null; NotifItem notifItem = null; int notifId = args.getInt(Constants.Setting.NOTIF_ID); String notifTitle = args.getString(Constants.Setting.NOTIF_TITLE); String notifText = args.getString(Constants.Setting.NOTIF_TEXT); if (notifId > 0){ notifItem = new NotifItem(); notifItem.setId(notifId); notifItem.setTicker(notifTitle); notifItem.setTitle(notifTitle); notifItem.setMessage(notifText); } switch (action){ case "com.htc.intent.action.QUICKBOOT_POWERON": case "android.intent.action.QUICKBOOT_POWERON": case "android.intent.action.BOOT_COMPLETED": case Constants.Action.CREATE_DAILY: createDailyAlarm(); break; case Constants.Action.SHOW_NOTIFY: if (notifId > 0) TestNotification.notify(this, notifItem); break; case Constants.Action.CLOSE_NOTIFY: if (notifId > 0) TestNotification.cancel(this, notifId); break; } TestReceiver.completeWakefulIntent(intent); } } private void createDailyAlarm(){ Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); AlarmManager alarmMgr; PendingIntent alarmIntent; alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, TestReceiver.class); intent.setAction(Constants.Action.SHOW_NOTIFY); Bundle args = new Bundle(); args.putInt(Constants.Setting.NOTIF_ID, 10002); args.putString(Constants.Setting.NOTIF_TITLE, "Test Notification"); args.putString(Constants.Setting.NOTIF_TEXT, "Test Notification for " + hour); intent.putExtras(args); alarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0); alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 60 * 60 * 1000, alarmIntent); ComponentName receiver = new ComponentName(this, TestReceiver.class); PackageManager pm = getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } }
Selanjutnya buka berkas manifest dengan mengklik ganda cabang manifests di panel Project. Lengkapi skripnya sehingga menjadi seperti di bawah ini.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.inochi.notification"> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="AllowBackup,GoogleAppIndexingWarning"> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".service.TestReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="com.inochi.notification.action.CREATE_DAILY" /> <action android:name="com.inochi.notification.action.CLOSE_NOTIFY" /> </intent-filter> </receiver> <service android:name=".service.MainService" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="com.inochi.notification.action.CREATE_DAILY" /> <action android:name="com.inochi.notification.action.CLOSE_NOTIFY" /> </intent-filter> </service> </application> </manifest>
Terakhir tinggal mengubah kode sumber untuk kelas MainActivity menjadi seperti di bawah ini.
package com.inochi.notification; import android.Manifest; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import com.inochi.notification.helper.BundleSettings; import com.inochi.notification.helper.Constants; import com.inochi.notification.item.NotifItem; import com.inochi.notification.service.TestReceiver; import com.inochi.notification.util.TestNotification; import java.util.Calendar; public class MainActivity extends AppCompatActivity { private BundleSettings bundleSettings; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); bundleSettings = new BundleSettings(this); setPermission(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { try { setPermission(); } catch (Exception e) { e.printStackTrace(); } } @SuppressLint("InlinedApi") private void setPermission(){ if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_BOOT_COMPLETED) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECEIVE_BOOT_COMPLETED}, Constants.Permission.Type.RECEIVE_BOOT_COMPLETED); } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.WAKE_LOCK) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WAKE_LOCK}, Constants.Permission.Type.WAKE_LOCK); } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.VIBRATE}, Constants.Permission.Type.VIBRATE); } else { if (bundleSettings.getBootPremission() == 0){ runAutoStartCustom(); } runService(); runFirstNotification(); } } private static final Intent[] AUTO_START_INTENTS = { new Intent().setComponent(new ComponentName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity")), new Intent("miui.intent.action.OP_AUTO_START").addCategory(Intent.CATEGORY_DEFAULT), new Intent().setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")), new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")), new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")), new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")), new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")), new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")), new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")), new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")), new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")), new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.entry.FunctionActivity")).setData( Uri.parse("mobilemanager://function/entry/AutoStart")) }; private void runAutoStartCustom(){ for (Intent intent : AUTO_START_INTENTS){ if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { startActivity(intent); bundleSettings.setBootPremission(1); break; } } } private void runService(){ Intent intent = new Intent(this, TestReceiver.class); intent.setAction(Constants.Action.CREATE_DAILY); sendBroadcast(intent); } private void runFirstNotification(){ Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); NotifItem notifItem = new NotifItem(); notifItem.setId(hour); notifItem.setTicker("Test Notification"); notifItem.setTitle("Test Notification"); notifItem.setMessage("Test Notification from Main Activity"); TestNotification.notify(this, notifItem); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
Jalankan aplikasi Anda. Pertama kali akan muncul notifikasi yang berasal dari MainActivity, dilanjutkan notifikasi yang menandakan proses service sedang berjalan. Selanjutnya tunggu satu jam berikutnya untuk ditampilkan kembali notifikasi.
Untuk pengujian, Anda dapat mempercepat proses menampilkan notifikasi ini dengan mengubah sedikit kode sumber dari kelas MainService di paket service. Pada baris ke 149, ubah nilai 60 * 60 * 1000 menjadi 60 * 1000, yang artinya Anda meminta notifikasi untuk ditampilkan setiap semenit sekali.
Anda dapat mengunduh contoh proyek melalui tautan berikut ini: https://github.com/InochiSoft/Android-Hourly-Notification