系統通話界面 自定義來電顯示

上一篇文章寫了使用WindowManager實現從自己的app界面撥打電話調用系統通話展示自定義佈局。這一篇文章主要介紹一下使用BroadcastReceiver、Service配合WindowManager實現當有電話打進來時,搜索該號碼是否是自己app內的用戶(好友),顯示特定的信息。

既然使用了BroadcastReceiver和Service,首先先介紹一下BroadcastReceiver和Service吧。

1.BroadcastReceiver

BroadcastReceiver顧名思義就是廣播接收器,廣播分爲廣播接收者和發送者,屬於Android四大組件之一,用於監聽 / 接收 應用 App 發出的廣播消息,並 做出響應。
BroadcastReceiver的註冊分爲兩種:靜態註冊和動態註冊。
(一)靜態註冊:在清單文件中通過標籤聲明,同時聲明要接收的action。
當此 App首次啓動時,系統會自動實例化該BroadcastReceiver類,並註冊到系統中,至此它的聲明週期就和該app沒什麼關係了,android不能自動銷燬廣播接收器,也就是說當應用程序關閉後,還是會接收廣播。
例如:

 <receiver android:name=".PhoneReceive">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
                <action android:name="com.ckw.CUSTOM_PHONE"/>
            </intent-filter>
        </receiver>

(二)動態註冊:在代碼中調用Context.registerReceiver()方法註冊,這裏不再多說。動態註冊的廣播接收器的聲明週期和宿主的聲明週期一致,當宿主被destroy了,它也就被銷燬了。

2.Service

Service也是Android的四大組件之一,可以使用Service在後臺執行耗時操作,例如下載。
Service的啓動方式也分爲兩種
(一)startService:
其他組件通過調用startService方法,啓動service。service會一直運行在後臺,即使啓動它的組件已經被銷燬了。
例如:

Intent ringingIntent = new Intent(mContext,PhoneService.class); 
ringingIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_RINGING);
mContext.startService(ringingIntent);

這種方式需要在清單文件中註冊服務:

<service android:name=".PhoneService"/>

(二)bindService:
其他組件調用bindService()方法綁定一個Service。多個組件可與一個service綁定,service不再與任何組件綁定時,該service會被destroy。
這種方式的service可以通過與Activity綁定,來實現與Activity的通信,這裏不再多說。

3.功能實現:
這裏用到了靜態註冊的BroadcastReceiver和startService方式創建的Service。
來電界面顯示的具體流程如圖:
這裏寫圖片描述

來電之後,通過App內部的廣播通過PhoneStateListener接收系統廣播的Action,
接着開啓Service,之後將App內需要展示的信息(通過本地讀取或者網絡請求拿到),
將這些信息傳給Service。讓Service創建WindowManager來繪製自定義的界面,並將信息顯示到來電界面。

下面直接貼上代碼吧:
(一):Service

public class PhoneService extends Service{
    /**
     * 用於在線程中創建或移除懸浮窗。
     */
    private Handler handler = new Handler();

    /**
     * 定時器,定時進行檢測當前應該創建還是移除懸浮窗。
     */
    private Timer timer;
    private int mPhoneState;//0 掛斷 1 來電 2 通話中 3 主動撥號

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mPhoneState = intent.getIntExtra("phoneState",0);
        String userName = "";
        String userDep = "";
        if(mPhoneState == 3 || mPhoneState == 1){//撥打電話 這裏後面還需要加個 1的類型
            userName = intent.getStringExtra("userName");
            userDep = intent.getStringExtra("userDep");
        }
        // 開啓定時器,每隔0.5秒刷新一次
        if (timer == null) {
            timer = new Timer();
            timer.scheduleAtFixedRate(new RefreshTask(userName,userDep), 0, 500);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Service被終止的同時也停止定時器繼續運行
        timer.cancel();
        timer = null;
    }

    class RefreshTask extends TimerTask {
        private String userName;
        private String userDep;
        public RefreshTask(String userName,String userDep) {
            this.userName = userName;
            this.userDep = userDep;
        }

        @Override
        public void run() {
            if(!isHome() && !PhoneWindowManager.isWindowShowing()){
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        PhoneWindowManager.createBigWindow(getApplicationContext(),userName,userDep);
                    }
                });
            }
            // 當前界面是桌面,且有懸浮窗顯示,則移除懸浮窗。
            if (isHome() || mPhoneState == 0) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        PhoneWindowManager.removeBigWindow(getApplicationContext());
                        Intent intent = new Intent(getApplicationContext(), PhoneService.class);
                        getApplicationContext().stopService(intent);
                    }
                });
            }

        }

    }


    /**
     * 判斷當前界面是否是桌面
     */
    private boolean isHome() {
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
        return getHomes().contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 獲得屬於桌面的應用的應用包名稱
     *
     * @return 返回包含所有包名的字符串列表
     */
    private List<String> getHomes() {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo) {
            names.add(ri.activityInfo.packageName);
        }
        return names;
    }


}

(二)BroadcastReceiver:

public class PhoneReceive extends BroadcastReceiver implements PhoneInfoContract.View{

    private Context mContext;

    @Inject
    PersonalAffairsApi personalAffairsApi;

    @Inject
    HttpManager httpManager;

    @Override
    public void onReceive(Context context, Intent intent) {
        mContext = context;

        if (intent.getAction().equals("com.ckw.CUSTOM_PHONE")) {//從自己的程序裏發出的,撥打電話
            String userName = intent.getStringExtra("userName");
            String userDep = intent.getStringExtra("userDep");
            Intent phoneIntent = new Intent(context, PhoneService.class);
            phoneIntent.putExtra("userName", userName);
            phoneIntent.putExtra("userDep", userDep);
            phoneIntent.putExtra("phoneState", 3);
            context.startService(phoneIntent);
        }
        //如果是去電
        else if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
            //主動撥打電話的話,會經過這裏,但是我們用上面的代碼,因爲需要傳自己的數據過來
        } else {
            //設置一個監聽器
            TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
            tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
        }
    }


    PhoneStateListener listener = new PhoneStateListener(){
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            //注意,方法必須寫在super方法後面,否則incomingNumber無法獲取到值。
            super.onCallStateChanged(state, incomingNumber);
            switch(state){
                case TelephonyManager.CALL_STATE_IDLE:
                    Intent phoneIntent = new Intent(mContext,PhoneService.class);
                    phoneIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_IDLE);
                    mContext.startService(phoneIntent);
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK://應該是打電話的狀態,通話中
                    break;
                case TelephonyManager.CALL_STATE_RINGING://響鈴,比如來電
                    String userId = DbHelper.getUserId(mContext);
                    PhoneInfoPresenter phoneInfoPresenter = new PhoneInfoPresenter(PhoneReceive.this,mContext);
                    phoneInfoPresenter.getPhoneInfo(incomingNumber,userId);
                    break;
            }
        }
    };


    @Override
    public void showPhoneInfoResult(PhoneInfo info) {
        //在這裏開啓service
        Intent ringingIntent = new Intent(mContext,PhoneService.class);
                    ringingIntent.putExtra("userName",info.getXm());
                    ringingIntent.putExtra("userDep",info.getDepname());
        ringingIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_RINGING);
        mContext.startService(ringingIntent);

    }

    @Override
    public boolean isActive() {
        return true;
    }

    @Override
    public void showError(String msg) {
        Log.d("----", "showError: "+msg);
    }

    @Override
    public void setPresenter(PhoneInfoPresenter presenter) {

    }



    @Override
    public void showProgressDialog(String msg) {

    }

    @Override
    public void hideProgressDialog() {

    }
}

(三)自定義的WindowManager:

public class PhoneWindowManager {

    /**
     * 顯示的view
     */
    private static FloatWindowView mFloatWindowView;


    /**
     * 懸浮窗View的參數
     */
    private static WindowManager.LayoutParams bigWindowParams;

    /**
     * 用於控制在屏幕上添加或移除懸浮窗
     */
    private static WindowManager mWindowManager;

    /**
     * 用於獲取手機可用內存
     */
    private static ActivityManager mActivityManager;



    /**
     * 創建一個懸浮窗。位置爲屏幕正中間。
     *
     * @param context
     *            必須爲應用程序的Context.
     */
    public static void createBigWindow(Context context,String userName,String userDep) {
        WindowManager windowManager = getWindowManager(context);
        if (mFloatWindowView == null) {
            mFloatWindowView = new FloatWindowView(context,userName,userDep);
            if (bigWindowParams == null) {
                bigWindowParams = new WindowManager.LayoutParams();
                bigWindowParams.x = 0;
                bigWindowParams.y = 0;
                bigWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                bigWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
                bigWindowParams.format = PixelFormat.RGBA_8888;
                bigWindowParams.gravity = Gravity.CENTER;
                bigWindowParams.height = FloatWindowView.viewHeight;
                bigWindowParams.width = FloatWindowView.viewWidth;
            }
            windowManager.addView(mFloatWindowView, bigWindowParams);
        }
    }

    /**
     * 將懸浮窗從屏幕上移除。
     *
     * @param context
     *            必須爲應用程序的Context.
     */
    public static void removeBigWindow(Context context) {
        if (mFloatWindowView != null) {
            WindowManager windowManager = getWindowManager(context);
            windowManager.removeView(mFloatWindowView);
            mFloatWindowView = null;
        }
    }


    /**
     * 是否有懸浮窗顯示在屏幕上。
     *
     * @return 有懸浮窗顯示在桌面上返回true,沒有的話返回false。
     */
    public static boolean isWindowShowing() {
        return mFloatWindowView != null;
    }

    /**
     * 如果WindowManager還未創建,則創建一個新的WindowManager返回。否則返回當前已創建的WindowManager。
     *
     * @param context
     *            必須爲應用程序的Context.
     * @return WindowManager的實例,用於控制在屏幕上添加或移除懸浮窗。
     */
    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

    /**
     * 如果ActivityManager還未創建,則創建一個新的ActivityManager返回。否則返回當前已創建的ActivityManager。
     *
     * @param context
     *            可傳入應用程序上下文。
     * @return ActivityManager的實例,用於獲取手機可用內存。
     */
    private static ActivityManager getActivityManager(Context context) {
        if (mActivityManager == null) {
            mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        }
        return mActivityManager;
    }

    /**
     * 獲取當前可用內存,返回數據以字節爲單位。
     *
     * @param context
     *            可傳入應用程序上下文。
     * @return 當前可用內存。
     */
    private static long getAvailableMemory(Context context) {
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        getActivityManager(context).getMemoryInfo(mi);
        return mi.availMem;
    }
}

(四)自定義的顯示佈局:
根據自己的需求來,這裏就不貼了。

發佈了69 篇原創文章 · 獲贊 24 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章