ภาพรวม
NotificationListenerService เป็น System Service ที่อนุญาตให้แอปอ่าน, กรอง, และตอบสนองต่อ Notification ของแอปอื่นๆ ต้องขอสิทธิ์พิเศษจากผู้ใช้ผ่าน Settings
⚠️ คำเตือน: การใช้ NotificationListenerService ต้องการความไว้วางใจจากผู้ใช้ ใช้เพื่อวัตถุประสงค์ที่ชัดเจน (Parental Control, Auto-reply, Backup) และต้องปฏิบัติตาม Play Store Policy อยเข้มงวด
ตัวอย่าง: สร้าง Notification Listener Service
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → New Project → Empty Activity (Package: app.tutorialspoint.com.notifyme)
ขั้นตอนที่ 2: สร้าง Interface สำหรับ Callback (MyListener.java)
package app.tutorialspoint.com.notifyme;
public interface MyListener {
void onNotificationPosted(String packageName, String title, String text);
void onNotificationRemoved(String packageName);
}
ขั้นตอนที่ 3: NotificationService.java (extends NotificationListenerService)
package app.tutorialspoint.com.notifyme;
import android.content.Context;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.annotation.RequiresApi;
import android.os.Build;
public class NotificationService extends NotificationListenerService {
private static final String TAG = "NotificationService";
private static MyListener listener;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Service created");
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
if (sbn.getNotification() == null) return;
String packageName = sbn.getPackageName();
String title = "";
String text = "";
// ดึงข้อมูลจาก Notification (API 16+)
android.app.Notification notification = sbn.getNotification();
Bundle extras = notification.extras;
if (extras != null) {
title = extras.getString(android.app.Notification.EXTRA_TITLE, "");
text = extras.getString(android.app.Notification.EXTRA_TEXT, "");
}
Log.i(TAG, "Posted: " + packageName + " | " + title + " | " + text);
if (listener != null) {
listener.onNotificationPosted(packageName, title, text);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
String packageName = sbn.getPackageName();
Log.i(TAG, "Removed: " + packageName);
if (listener != null) {
listener.onNotificationRemoved(packageName);
}
}
public static void setListener(MyListener listener) {
NotificationService.listener = listener;
}
@Override
public void onListenerConnected() {
super.onListenerConnected();
Log.d(TAG, "Listener connected");
// ดึง Notification ที่มีอยู่แล้ว (Optional)
StatusBarNotification[] active = getActiveNotifications();
for (StatusBarNotification sbn : active) {
onNotificationPosted(sbn);
}
}
@Override
public void onListenerDisconnected() {
super.onListenerDisconnected();
Log.d(TAG, "Listener disconnected");
listener = null;
}
}
ขั้นตอนที่ 4: Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btnCreateNotification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Create Test Notification" />
<Button
android:id="@+id/btnOpenSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/btnCreateNotification"
android:layout_marginTop="8dp"
android:text="Open Notification Access Settings" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/btnOpenSettings"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Notification Listener Service Example\nWaiting for notifications..."
android:textAppearance="?android:attr/textAppearanceMedium" />
</ScrollView>
</RelativeLayout>
ขั้นตอนที่ 5: MainActivity.java
package app.tutorialspoint.com.notifyme;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements MyListener {
private TextView txtView;
public static final String NOTIFICATION_CHANNEL_ID = "10001";
private static final String DEFAULT_CHANNEL_ID = "default";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtView = findViewById(R.id.textView);
// ลงทะเบียน Listener
NotificationService.setListener(this);
Button btnCreateNotification = findViewById(R.id.btnCreateNotification);
btnCreateNotification.setOnClickListener(v -> createTestNotification());
Button btnOpenSettings = findViewById(R.id.btnOpenSettings);
btnOpenSettings.setOnClickListener(v -> openNotificationAccessSettings());
}
private void createTestNotification() {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Test Notification")
.setContentText("Notification Listener Service Example")
.setTicker("Test Ticker")
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "Test Channel", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
}
manager.notify((int) System.currentTimeMillis(), builder.build());
}
private void openNotificationAccessSettings() {
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(intent);
}
@Override
public void onNotificationPosted(String packageName, String title, String text) {
runOnUiThread(() -> {
String log = "\n[POSTED] " + packageName + "\nTitle: " + title + "\nText: " + text;
txtView.append(log);
// Auto scroll
// scrollView.fullScroll(View.FOCUS_DOWN); // ถ้ามี ScrollView
});
}
@Override
public void onNotificationRemoved(String packageName) {
runOnUiThread(() -> {
txtView.append("\n[REMOVED] " + packageName);
});
}
@Override
protected void onDestroy() {
super.onDestroy();
NotificationService.setListener(null); // ป้องกัน Memory Leak
}
}
ขั้นตอนที่ 6: AndroidManifest.xml - สำคัญมาก
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.tutorialspoint.com.notifyme">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- สำคัญ: Service ต้องมี Permission นี้ -->
<service
android:name=".NotificationService"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>
ขั้นตอนการเปิดใช้งาน (สำคัญมาก)
- ติดตั้งแอปบนอุปกรณ์
- ไป **Settings → Apps → Special App Access → Notification access** (หรือ Settings → Notifications → Notification access)
- หาแอปของคุณ → Toggle **ON**
- ยืนยัน Dialog "Allow access to notifications?"
- กลับมาแอป → กด "Create Test Notification" → ดู Log ใน TextView
ข้อมูลที่เข้าถึงได้จาก StatusBarNotification
StatusBarNotification sbn; // Package name sbn.getPackageName(); // Notification ID sbn.getId(); // Tag sbn.getTag(); // UID sbn.getUid(); // User Handle sbn.getUser(); // Notification object Notification notification = sbn.getNotification(); // Extras (Title, Text, BigText, etc.) Bundle extras = notification.extras; String title = extras.getString(Notification.EXTRA_TITLE); String text = extras.getString(Notification.EXTRA_TEXT); CharSequence bigText = extras.getCharSequence(Notification.EXTRA_BIG_TEXT); // Actions notification.actions // Array of Action // Post time sbn.getPostTime();
การจัดการ Notification (API 18+)
// ลบ Notification ของแอปอื่น (ต้องมี Permission) cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId()); // ลบทั้งหมดของ Package cancelAllNotifications(packageName);
ข้อจำกัดและข้อควรระวัง
- Permission: ต้องได้รับอนุญาตจาก User ทุกครั้งที่ติดตั้ง/อัปเดต
- Privacy: Google Play ตรวจสอบเข้มงวด ต้องระบุเหตุผลใน Play Console
- Performance: onNotificationPosted เรียกบน Main Thread ของ Service → ห้ามทำงานหนัก ใช้ Handler/Executor
- Reliability: Service อาจถูก Kill โดยระบบ → ใช้
android:foregroundServiceType="dataSync"หรือ Start Foreground - Android 12+: ต้องการ Permission เพิ่มเติมสำหรับการเข้าถึงข้อมูลบางอย่าง
กรณีใช้งานจริง (Use Cases)
- แอปควบคุมผู้ปกครอง (Parental Control)
- แอปสำรองข้อความ (SMS Backup)
- แอปตอบอัตโนมัติ (Auto-reply bot)
- แอปจัดการ Notification (Grouper, Filter)
- Smartwatch Companion App
ทางเลือกอื่น: หากต้องการแค่ดู Notification ของตัวเอง ไม่ต้องใช้ Listener Service ให้ใช้ NotificationManager.getActiveNotifications() (API 23+) แทน