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

Chặn và đọc Notification thanh trạng thái với NotificationListenerService

NotificationListenerService cho phép app đọc, lọc, hủy hoặc phản ứng với tất cả notification xuất hiện trên thanh trạng thái (status bar). Yêu cầu user cấp quyền "Truy cập thông báo" (Notification Access) trong Cài đặt.

Kiến trúc giải pháp

  • MyListener Interface: Callback từ Service về Activity.
  • NotificationService: Kế thừa NotificationListenerService, override onNotificationPosted() và onNotificationRemoved().
  • MainActivity: Implement Listener, hiển thị log, mở cài đặt cấp quyền.

Các bước thực hiện

Bước 1: Tạo dự án mới

Package: app.tutorialspoint.com.notifyme.

Bước 2: Interface Callback (src/MyListener.java)

package app.tutorialspoint.com.notifyme;

public interface MyListener {
    void setValue(String packageName);
}

Bước 3: NotificationListenerService (src/NotificationService.java)

package app.tutorialspoint.com.notifyme;

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 = "NotificationService";
    private Context context;
    private static MyListener myListener;

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

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        Log.i(TAG, "********** onNotificationPosted");
        Log.i(TAG, "ID: " + sbn.getId() 
            + " | Package: " + sbn.getPackageName()
            + " | Ticker: " + sbn.getNotification().tickerText);
        
        if (myListener != null) {
            myListener.setValue("Posted: " + sbn.getPackageName());
        }
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        Log.i(TAG, "********** onNotificationRemoved");
        Log.i(TAG, "ID: " + sbn.getId() 
            + " | Package: " + sbn.getPackageName()
            + " | Ticker: " + sbn.getNotification().tickerText);
        
        if (myListener != null) {
            myListener.setValue("Removed: " + sbn.getPackageName());
        }
    }

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

Bước 4: Menu Settings (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"
    xmlns:tools="https://schemas.android.com/tools"
    tools:context=".MainActivity">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="Cài đặt quyền Notification"
        app:showAsAction="never" />
</menu>

Bước 5: Giao diện chính (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="Tạo Notification Test" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/btnCreateNotification"
        android:layout_alignStart="@+id/btnCreateNotification"
        android:layout_alignEnd="@+id/btnCreateNotification"
        android:layout_alignParentBottom="true">
        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="NotificationListenerService Example"
            android:textAppearance="?android:attr/textAppearanceMedium" />
    </ScrollView>
</RelativeLayout>

Bước 6: MainActivity (src/MainActivity.java)

package app.tutorialspoint.com.notifyme;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
import androidx.appcompat.app.AppCompatActivity;
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 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);

        // Đăng ký listener
        new NotificationService().setListener(this);

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

    private void createTestNotification() {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
            .setContentTitle("My Notification")
            .setContentText("Notification Listener Service Example")
            .setTicker("Notification Listener Service Example")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setAutoCancel(true);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel channel = new NotificationChannel(
                NOTIFICATION_CHANNEL_ID, "Test Channel", importance);
            builder.setChannelId(NOTIFICATION_CHANNEL_ID);
            if (manager != null) {
                manager.createNotificationChannel(channel);
            }
        }

        if (manager != null) {
            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ài đặt "Notification Access"
            Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
            startActivity(intent);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void setValue(String packageName) {
        runOnUiThread(() -> txtView.append("\n" + packageName));
    }
}

Bước 7: AndroidManifest.xml (Quan trọng: khai báo Service với permission)

<?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" />

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

        <!-- QUAN TRỌNG: Service phải có permission BIND_NOTIFICATION_LISTENER_SERVICE -->
        <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>

Cách chạy và test

  1. Cài app lên thiết bị.
  2. Mở app, nhấn menu (3 chấm) → "Cài đặt quyền Notification".
  3. Tìm app trong danh sách, bật toggle "Cho phép truy cập thông báo".
  4. Quay lại app, nhấn "Tạo Notification Test".
  5. Xem log trong TextView: sẽ hiển thị "Posted: package.name" khi notification xuất hiện, "Removed: package.name" khi xóa.

Ứng dụng thực tế

  • App đọc tin nhắn SMS/Email tự động (OTP, mã xác thực).
  • App quản lý thông báo: lọc, ẩn, gom nhóm, tự động trả lời.
  • App bảo mật: cảnh báo khi app lạ đăng notification nhạy cảm.
  • Wear OS: đồng bộ notification sang đồng hồ thông minh.

Lưu ý bảo mật & quyền riêng tư

  • User phải chủ động bật quyền "Notification Access" - không thể ép buộc.
  • App có thể đọc toàn bộ nội dung notification (bao gồm tin nhắn riêng tư, OTP, email).
  • Google Play yêu cầu chính sách quyền riêng tư rõ ràng, lý do cần quyền này.
  • Từ Android 11 (API 30), có thể dùng NotificationListenerService.Ranking để biết mức độ quan trọng.