Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách phát hiện khi có thông báo mới được đăng (NotificationListenerService)

Giới thiệu

Sử dụng NotificationListenerService để lắng nghe tất cả thông báo xuất hiện/biến mất trên thiết bị. Cần quyền đặc biệt từ người dùng (Notification Access).

Yêu cầu

  • Android 4.3+ (API 18)
  • Người dùng bật quyền: Settings → Apps → Special access → Notification access → Cho phép app

Kiến trúc

  1. MyListener Interface: Callback về MainActivity.
  2. NotificationService (extends NotificationListenerService): Override onNotificationPosted(), onNotificationRemoved().
  3. MainActivity: Implement Listener, hiển thị log, mở cài đặt quyền.

Code mẫu

1. MyListener.java (Interface)

package com.example.notificationlistener;

public interface MyListener {
    void onNotificationEvent(String event); // "Post: com.package" hoặc "Remove: com.package"
}

2. NotificationService.java

package com.example.notificationlistener;

import android.content.Context;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;

public class NotificationService extends NotificationListenerService {

    private static final String TAG = "NotifListener";
    private static MyListener listener;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        String pkg = sbn.getPackageName();
        String text = "";
        if (sbn.getNotification().extras != null) {
            text = sbn.getNotification().extras.getString("android.text", "");
        }
        Log.i(TAG, "Posted: " + pkg + " - " + text);
        if (listener != null) {
            listener.onNotificationEvent("POST: " + pkg + " | " + text);
        }
    }

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

    public static void setListener(MyListener l) {
        listener = l;
    }
}

3. 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="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:text="Tạo thông báo test" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/btnCreateNotification">

        <TextView
            android:id="@+id/txtLog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Notification Listener Log"
            android:textAppearance="?android:attr/textAppearanceMedium" />

    </ScrollView>
</RelativeLayout>

4. MainActivity.java

package com.example.notificationlistener;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements MyListener {

    private TextView txtLog;
    private static final String CHANNEL_ID = "test_channel";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txtLog = findViewById(R.id.txtLog);
        NotificationService.setListener(this);

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

    private void createTestNotification() {
        NotificationManager manager = getSystemService(NotificationManager.class);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle("Test Listener")
                .setContentText("Kiểm tra NotificationListenerService")
                .setAutoCancel(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Test", NotificationManager.IMPORTANCE_DEFAULT);
            manager.createNotificationChannel(channel);
            builder.setChannelId(CHANNEL_ID);
        }
        manager.notify((int) System.currentTimeMillis(), builder.build());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.action_settings) {
            // Mở màn hình cấp quyền Notification Access
            startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onNotificationEvent(String event) {
        runOnUiThread(() -> txtLog.append("\n" + event));
    }
}

5. res/menu/menu_main.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_settings"
        android:title="Cấp quyền Notification Access"
        app:showAsAction="never" />
</menu>

6. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.notificationlistener">

    <application ...>
        <activity android:name=".MainActivity" android:exported="true">
            <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">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>
    </application>
</manifest>

Quy trình test

  1. Chạy app, nhấn menu "Cấp quyền Notification Access".
  2. Tìm app trong danh sách, bật toggle cho phép.
  3. Quay lại app, nhấn "Tạo thông báo test".
  4. Log hiển thị "POST: package.name | nội dung".
  5. Vuốt xóa thông báo → Log hiển thị "REMOVE: package.name".

Ứng dụng thực tế

  • App đọc tin nhắn SMS/Chat (auto-reply, backup).
  • App quản lý thông báo (lọc, gom nhóm, ưu tiên).
  • App phụ huynh kiểm soát (theo dõi thông báo con).
⚠️ Quyền riêng tư: Google Play yêu cầu khai báo lý do sử dụng quyền nhạy cảm này. Chỉ dùng khi thực sự cần thiết.