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

วิธีรับสายเข้าโดยอัตโนมัติแบบโปรแกรมใน Android


ภาพรวม

ตัวอย่างนี้แสดงวิธีรับสายโทรศัพท์เข้าโดยอัตโนมัติ (Auto Answer) ใน Android โดยใช้ Intent.ACTION_MEDIA_BUTTON และ KeyEvent.KEYCODE_HEADSETHOOK ซึ่งจำเป็นต้องมีสิทธิ์ CALL_PRIVILEGED

⚠️ ข้อควรระวังสำคัญ

ข้อจำกัด:
  • ต้องการสิทธิ์ android.permission.CALL_PRIVILEGED ซึ่งเป็น Signature Permission - แอปทั่วไปไม่สามารถขอได้
  • ทำงานได้เฉพาะแอปที่ลงนามด้วย System Key หรือ Pre-installed apps เท่านั้น
  • Android 8.0+ (API 26+) จำกัดการเริ่ม Activity จาก Background
  • Google Play Policy ห้ามแอปสาธารณะใช้สิทธิ์นี้

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

เปิด Android Studio ไปที่ File → New Project แล้วกรอกข้อมูลที่จำเป็น

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Auto Answer Demo (ต้องเป็น System App)"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

ขั้นตอนที่ 3: MainActivity (src/MainActivity.java)

package com.app.sample;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void acceptCall() {
        Context context = this; // ใช้ this แทน null
        
        if (Build.VERSION.SDK_INT >= 21) {
            Intent answerCallIntent = new Intent(context, AcceptCallActivity.class);
            answerCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                                    Intent.FLAG_ACTIVITY_CLEAR_TASK | 
                                    Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            context.startActivity(answerCallIntent);
        } else {
            Intent intent = new Intent(context, AcceptCallActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                          Intent.FLAG_ACTIVITY_CLEAR_TASK | 
                          Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            context.startActivity(intent);
        }
    }
}

ขั้นตอนที่ 4: AcceptCallActivity (src/AcceptCallActivity.java)

package com.app.sample;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.WindowManager;
import java.io.IOException;

public class AcceptCallActivity extends Activity {
    private static final String MANUFACTURER_HTC = "HTC";
    private KeyguardManager keyguardManager;
    private AudioManager audioManager;
    private CallStateReceiver callStateReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    protected void onResume() {
        super.onResume();
        registerCallStateReceiver();
        updateWindowFlags();
        acceptCall();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (callStateReceiver != null) {
            unregisterReceiver(callStateReceiver);
            callStateReceiver = null;
        }
    }

    private void registerCallStateReceiver() {
        callStateReceiver = new CallStateReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
        registerReceiver(callStateReceiver, intentFilter);
    }

    private void updateWindowFlags() {
        if (keyguardManager.inKeyguardRestrictedInputMode()) {
            getWindow().addFlags(
                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        } else {
            getWindow().clearFlags(
                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        }
    }

    private void acceptCall() {
        boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER) 
                                   && !audioManager.isWiredHeadsetOn();
        
        if (broadcastConnected) {
            broadcastHeadsetConnected(false);
        }
        
        try {
            try {
                Runtime.getRuntime().exec("input keyevent " + KeyEvent.KEYCODE_HEADSETHOOK);
            } catch (IOException e) {
                String enforcedPerm = "android.permission.CALL_PRIVILEGED";
                Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON)
                    .putExtra(Intent.EXTRA_KEY_EVENT, 
                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
                Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON)
                    .putExtra(Intent.EXTRA_KEY_EVENT, 
                        new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
                sendOrderedBroadcast(btnDown, enforcedPerm);
                sendOrderedBroadcast(btnUp, enforcedPerm);
            }
        } finally {
            if (broadcastConnected) {
                broadcastHeadsetConnected(false);
            }
        }
    }

    private void broadcastHeadsetConnected(boolean connected) {
        Intent i = new Intent(Intent.ACTION_HEADSET_PLUG);
        i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        i.putExtra("state", connected ? 1 : 0);
        i.putExtra("name", "mysms");
        try {
            sendOrderedBroadcast(i, null);
        } catch (Exception e) {
            // Ignore
        }
    }

    private class CallStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            finish(); // ปิด Activity เมื่อสถานะสายเปลี่ยน
        }
    }
}

ขั้นตอนที่ 5: Manifest (Manifests/AndroidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" 
    package="com.app.sample">
    
    <!-- สิทธิ์ที่ต้องการ (CALL_PRIVILEGED ต้องเป็น System App) -->
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" /> <!-- API 26+ -->
    
    <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>
        
        <activity android:name=".AcceptCallActivity"
            android:excludeFromRecents="true"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
            
    </application>
</manifest>

ทางเลือกที่แนะนำสำหรับแอปทั่วไป

แทนที่จะพยายาม Auto Answer (ซึ่งไม่ได้ในแอปทั่วไป) ควรใช้:

  • Call Screening API (Android 10+) - ใช้ CallScreeningService กรอง/บล็อกสาย
  • ConnectionService - สำหรับแอป VoIP/Dialer
  • InCallService - แทนที่ Dialer ระบบ (ต้องเป็น Default Dialer)

การทดสอบ

โค้ดนี้ทำงานได้เฉพาะบนอุปกรณ์ที่ Root แล้ว หรือแอปที่ติดตั้งเป็น System App เท่านั้น บนแอปทั่วไปจะไม่สามารถรับสายได้