Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีอ่าน Notification ทั้งหมดที่เข้ามาใน Android

ภาพรวม

บทความนี้แสดงวิธีใช้ NotificationListenerService เพื่ออ่าน Notification ทุกข้อความที่เข้ามาบนอุปกรณ์ คล้ายบทความก่อนหน้า (ID: 1004061542) แต่เน้นที่การ "อ่านทั้งหมด" รวมถึง Notification ที่มีอยู่แล้วตอนเริ่ม Service

ความแตกต่างจากบทความก่อนหน้า

  • ใช้ onListenerConnected() ดึง Notification Active ที่มีอยู่แล้ว
  • แสดงข้อมูลครบถ้วน: Title, Text, BigText, Time, Package, UID
  • จัดการ Lifecycle ให้ถูกต้อง (Register/Unregister Listener)

โค้ดหลัก (เหมือนบทความก่อนหน้า แต่เพิ่มรายละเอียด)

NotificationService.java - เวอร์ชันครบถ้วน

package app.tutorialspoint.com.notifyme;

import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.annotation.RequiresApi;

public class NotificationService extends NotificationListenerService {
    private static final String TAG = "NotifListenerService";
    private static NotificationListener listener;

    public interface NotificationListener {
        void onNotificationReceived(NotificationData data);
        void onNotificationRemoved(String packageName, int id);
        void onListenerConnected();
    }

    public static class NotificationData {
        public String packageName;
        public String title;
        public String text;
        public String bigText;
        public long postTime;
        public int id;
        public String tag;
        public int uid;
        
        @Override
        public String toString() {
            return "[" + packageName + "] " + title + ": " + text + 
                   (bigText != null && !bigText.isEmpty() ? " | Big: " + bigText : "") +
                   " | Time: " + postTime;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service created");
    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        NotificationData data = extractNotificationData(sbn);
        Log.i(TAG, "Posted: " + data.toString());
        if (listener != null) {
            listener.onNotificationReceived(data);
        }
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        Log.i(TAG, "Removed: " + sbn.getPackageName() + " ID: " + sbn.getId());
        if (listener != null) {
            listener.onNotificationRemoved(sbn.getPackageName(), sbn.getId());
        }
    }

    @Override
    public void onListenerConnected() {
        super.onListenerConnected();
        Log.d(TAG, "Listener connected - fetching active notifications");
        
        // ดึง Notification ที่ Active อยู่แล้ว (ก่อน Service เริ่มทำงาน)
        StatusBarNotification[] activeNotifications = getActiveNotifications();
        for (StatusBarNotification sbn : activeNotifications) {
            NotificationData data = extractNotificationData(sbn);
            Log.i(TAG, "Active: " + data.toString());
            if (listener != null) {
                listener.onNotificationReceived(data);
            }
        }
        
        if (listener != null) {
            listener.onListenerConnected();
        }
    }

    @Override
    public void onListenerDisconnected() {
        super.onListenerDisconnected();
        Log.d(TAG, "Listener disconnected");
        listener = null;
    }

    private NotificationData extractNotificationData(StatusBarNotification sbn) {
        NotificationData data = new NotificationData();
        data.packageName = sbn.getPackageName();
        data.id = sbn.getId();
        data.tag = sbn.getTag();
        data.uid = sbn.getUid();
        data.postTime = sbn.getPostTime();
        
        android.app.Notification notification = sbn.getNotification();
        if (notification != null && notification.extras != null) {
            Bundle extras = notification.extras;
            data.title = extras.getString(android.app.Notification.EXTRA_TITLE, "");
            data.text = extras.getString(android.app.Notification.EXTRA_TEXT, "");
            data.bigText = extras.getString(android.app.Notification.EXTRA_BIG_TEXT, "");
            // ข้อมูลเพิ่มเติม
            // CharSequence[] lines = extras.getCharSequenceArray(Notification.EXTRA_LINES); // InboxStyle
            // Bitmap picture = (Bitmap) extras.getParcelable(Notification.EXTRA_LARGE_ICON); // BigPicture
        }
        return data;
    }

    public static void setListener(NotificationListener listener) {
        NotificationService.listener = listener;
    }
}

MainActivity.java - แสดงผลแบบ Real-time

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.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements NotificationService.NotificationListener {
    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);
        txtView.setMovementMethod(new ScrollingMovementMethod()); // เลื่อนอ่านได้
        
        NotificationService.setListener(this);

        Button btnCreateNotification = findViewById(R.id.btnCreateNotification);
        btnCreateNotification.setOnClickListener(v -> createTestNotification());

        Button btnOpenSettings = findViewById(R.id.btnOpenSettings);
        btnOpenSettings.setOnClickListener(v -> openNotificationAccessSettings());

        Button btnClear = findViewById(R.id.btnClear);
        btnClear.setOnClickListener(v -> txtView.setText("Cleared\n"));
    }

    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 from " + System.currentTimeMillis())
                .setContentText("Short text")
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText("This is a very long big text that demonstrates reading big text content from notification extras. " +
                                "It can contain multiple lines and detailed information."))
                .setTicker("Ticker text")
                .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 onNotificationReceived(NotificationService.NotificationData data) {
        runOnUiThread(() -> {
            String log = "\n--- NEW ---\n" + data.toString();
            txtView.append(log);
        });
    }

    @Override
    public void onNotificationRemoved(String packageName, int id) {
        runOnUiThread(() -> {
            txtView.append("\n--- REMOVED ---\n" + packageName + " ID: " + id);
        });
    }

    @Override
    public void onListenerConnected() {
        runOnUiThread(() -> txtView.append("\n>>> Listener Connected - Reading active notifications..."));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        NotificationService.setListener(null);
    }
}

Layout เพิ่มปุ่ม Clear (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">

    <LinearLayout
        android:id="@+id/buttonContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_alignParentTop="true">

        <Button
            android:id="@+id/btnCreateNotification"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Create Test Notification" />

        <Button
            android:id="@+id/btnOpenSettings"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Open Notification Access Settings" />

        <Button
            android:id="@+id/btnClear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Clear Log"
            android:backgroundTint="#FF9800" />
    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/buttonContainer"
        android:layout_marginTop="16dp">

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Notification Listener Service - Reading All Notifications\nWaiting..."
            android:textAppearance="?android:attr/textAppearanceMedium" />
    </ScrollView>
</RelativeLayout>

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
            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>

ขั้นตอนการทดสอบ

  1. ติดตั้งและรันแอป
  2. กด "Open Notification Access Settings" → เปิด Permission ให้แอป
  3. กลับมาแอป → จะเห็น "Listener Connected" และ Notification ที่ Active อยู่แล้ว
  4. กด "Create Test Notification" หลายครั้ง → ดู Log เพิ่มขึ้นทีละข้อความ
  5. สวายปิด Notification จาก Shade → ดู Event "REMOVED"
  6. ลองส่ง Notification จากแอปอื่น (Line, Facebook, SMS) → จะถูกจับได้ทั้งหมด

ข้อมูลที่อ่านได้จาก Notification Extras

Key (Notification.EXTRA_*)ชนิดคำอธิบาย
EXTRA_TITLEStringหัวข้อ Notification
EXTRA_TEXTStringข้อความหลัก
EXTRA_BIG_TEXTStringข้อความขยาย (BigTextStyle)
EXTRA_SUB_TEXTStringข้อความรอง
EXTRA_SUMMARY_TEXTStringข้อความสรุป (Inbox/BigPicture)
EXTRA_LINESCharSequence[]รายการบรรทัด (InboxStyle)
EXTRA_LARGE_ICONBitmapLarge Icon
EXTRA_PICTUREBitmapBig Picture
EXTRA_PEOPLEString[]รายชื่อคน (MessagingStyle)
EXTRA_MESSAGESParcelable[]ข้อความแชท (MessagingStyle)

การจัดการข้อมูล Notification (Best Practices)

  1. Filter Package: เก็บเฉพาะ Package ที่สนใจ (Whitelist)
  2. Debounce: หลีกเลี่ยงการประมวลผลซ้ำ (Key: pkg + id + tag)
  3. Background Thread: ประมวลผลข้อมูลหนักใน Background Thread
  4. Storage: บันทึกลง Room Database พร้อม Timestamp
  5. Privacy: ไม่ Log ข้อมูลส่วนตัว, เข้ารหัสถ้าบันทึก

ข้อจำกัดสำคัญ

  • ไม่สามารถอ่าน Notification ที่มาก่อน Service เริ่มทำงาน (ยกเว้น Active Notifications)
  • ไม่สามารถอ่าน Notification ที่ User Dismiss แล้ว (ก่อน Listener Connected)
  • บางแอปตั้ง Flag FLAG_NO_CLEAR หรือเป็น System Notification อาจเข้าถึงไม่ได้
  • Android 12+ จำกัดการเข้าถึง EXTRA_MESSAGES, EXTRA_PEOPLE ต้องการ Permission เพิ่ม
Play Store Policy: แอปที่ใช้ NotificationListenerService ต้องระบุเป็น "Core Functionality" ใน Play Console และผ่านการตรวจสอบพิเศษ ห้ามใช้เพื่อเก็บข้อมูลขาย/โฆษณา