編輯:關於Android編程
在開機時,系統會啟動PhoneApp類,那是因為在AndroidManifest.xml文件中配置了
由於配置了android:persistent="true"屬性,並且Phone.apk是安裝在/system/app/目錄下的,所以在開機時會自動啟動PhoneApp類。在PhoneApp初始化時會進入回調函數:onCreate()
@Override public void onCreate() { //....... if (phone == null) { //........ // Create the CallNotifer singleton, which handles // asynchronous events from the telephony layer (like // launching the incoming-call UI when an incoming call comes // in.) notifier = CallNotifier.init(this, phone, ringer, mBtHandsfree, new CallLogAsync()); //........ } }對CallNotifier對象進行初始化,Callnotifier主要是對電話狀態的監聽
在CallNotifier的構造函數裡
private CallNotifier(PhoneApp app, Phone phone, Ringer ringer, BluetoothHandsfree btMgr, CallLogAsync callLog) { //............ //跟CallManager注冊通知,跟Framework通訊 registerForNotifications(); //............... }在CallNotifier.java中向CallManager類(Framework層)注冊監聽消息
private void registerForNotifications() { mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null); mCM.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null); mCM.registerForDisconnect(this, PHONE_DISCONNECT, null); mCM.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null); mCM.registerForIncomingRing(this, PHONE_INCOMING_RING, null); mCM.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null); mCM.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null); mCM.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null); mCM.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null); mCM.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null); mCM.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null); mCM.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null); mCM.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null); }通過mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);監聽來電,在CallManager.java中
/**
* Register for getting notifications for change in the Call State {@link Call.State}
* This is called PreciseCallState because the call state is more precise than the
* {@link Phone.State} which can be obtained using the {@link PhoneStateListener}
*
* Resulting events will have an AsyncResult in Message.obj
.
* AsyncResult.userData will be set to the obj argument here.
* The h parameter is held only by a weak reference.
*/
public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){
mPreciseCallStateRegistrants.addUnique(h, what, obj);
}
處理PHONE_NEW_RINGING_CONNECTION
@Override public void handleMessage(Message msg) { switch (msg.what) { case PHONE_NEW_RINGING_CONNECTION: log("RINGING... (new)"); onNewRingingConnection((AsyncResult) msg.obj); mSilentRingerRequested = false; break; //........... } }
什麼時候會收到PHONE_NEW_RINGING_CONNECTION消息呢?當Modem(調制解調器)端收到來電信息時,會將相關來電信息通過AT指令發送給RILC,再通過RILC使用socket發送給RILJ,逐層向上傳遞,上傳到Framework層,最終顯示來電響鈴界面。最後是通過以下廣播上傳到PhoneApp
在RIL中有一個內部類RILReceiver
class RILReceiver implements Runnable { byte[] buffer; RILReceiver() { buffer = new byte[RIL_MAX_COMMAND_BYTES]; } public void run() { int retryCount = 0; try {for (;;) { LocalSocket s = null; LocalSocketAddress l; try { s = new LocalSocket(); l = new LocalSocketAddress(SOCKET_NAME_RIL, LocalSocketAddress.Namespace.RESERVED); s.connect(l); } catch (IOException ex){ try { if (s != null) { s.close(); } } catch (IOException ex2) { //ignore failure to close after failure to connect } // don't print an error message after the the first time // or after the 8th time if (retryCount == 8) { Log.e (LOG_TAG, "Couldn't find '" + SOCKET_NAME_RIL + "' socket after " + retryCount + " times, continuing to retry silently"); } else if (retryCount > 0 && retryCount < 8) { Log.i (LOG_TAG, "Couldn't find '" + SOCKET_NAME_RIL + "' socket; retrying after timeout"); } try { Thread.sleep(SOCKET_OPEN_RETRY_MILLIS); } catch (InterruptedException er) { } retryCount++; continue; } retryCount = 0; mSocket = s; Log.i(LOG_TAG, "Connected to '" + SOCKET_NAME_RIL + "' socket"); int length = 0; try { InputStream is = mSocket.getInputStream(); for (;;) { Parcel p; length = readRilMessage(is, buffer); if (length < 0) { // End-of-stream reached break; } p = Parcel.obtain(); p.unmarshall(buffer, 0, length); p.setDataPosition(0); //Log.v(LOG_TAG, "Read packet: " + length + " bytes"); processResponse(p); p.recycle(); } } catch (java.io.IOException ex) { Log.i(LOG_TAG, "'" + SOCKET_NAME_RIL + "' socket closed", ex); } catch (Throwable tr) { Log.e(LOG_TAG, "Uncaught exception read length=" + length + "Exception:" + tr.toString()); } Log.i(LOG_TAG, "Disconnected from '" + SOCKET_NAME_RIL + "' socket"); setRadioState (RadioState.RADIO_UNAVAILABLE); try { mSocket.close(); } catch (IOException ex) { } mSocket = null; RILRequest.resetSerial(); // Clear request list on close clearRequestsList(RADIO_NOT_AVAILABLE, false); }} catch (Throwable tr) { Log.e(LOG_TAG,"Uncaught exception", tr); } /* We're disconnected so we don't know the ril version */ notifyRegistrantsRilConnectionChanged(-1); } }在RIL的構造函數中創建RILReceiver對象public RIL(Context context, int preferredNetworkType, int cdmaSubscription) { //.................. mReceiver = new RILReceiver(); mReceiverThread = new Thread(mReceiver, "RILReceiver"); mReceiverThread.start(); //................. } }在前面的分析中知道RIL在PhoneApp中就進行初始化了,RILReceiver是一個線程使用Socket通信。在線程中調用processResponse(p)
private void processResponse (Parcel p) { int type; type = p.readInt(); if (type == RESPONSE_UNSOLICITED) { //主動響應 processUnsolicited (p); } else if (type == RESPONSE_SOLICITED) { //響應請求 processSolicited (p); } releaseWakeLockIfDone(); }來電調用的是以下函數private void processUnsolicited (Parcel p) { //.............. case RIL_UNSOL_CALL_RING: ret = responseCallRing(p); break; //.............. case RIL_UNSOL_CALL_RING: if (RILJ_LOGD) unsljLogRet(response, ret); if (mRingRegistrant != null) { mRingRegistrant.notifyRegistrant( new AsyncResult (null, ret, null)); } break; //.............. }進入Registrant類中
public void notifyRegistrant(AsyncResult ar){ internalNotifyRegistrant (ar.result, ar.exception); }/*package*/ void internalNotifyRegistrant (Object result, Throwable exception) { Handler h = getHandler(); if (h == null) { clear(); } else { Message msg = Message.obtain(); msg.what = what; msg.obj = new AsyncResult(userObj, result, exception); h.sendMessage(msg); } }當PhoneApp收到:PHONE_NEW_RINGING_CONNECTION後/** * Handles a "new ringing connection" event from the telephony layer. */ private void onNewRingingConnection(AsyncResult r) { Connection c = (Connection) r.result; log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }"); Call ringing = c.getCall(); Phone phone = ringing.getPhone(); // Check for a few cases where we totally ignore incoming calls. if (ignoreAllIncomingCalls(phone)) { // Immediately reject the call, without even indicating to the user // that an incoming call occurred. (This will generally send the // caller straight to voicemail, just as if we *had* shown the // incoming-call UI and the user had declined the call.) PhoneUtils.hangupRingingCall(ringing); return; } if (c == null) { Log.w(LOG_TAG, "CallNotifier.onNewRingingConnection(): null connection!"); // Should never happen, but if it does just bail out and do nothing. return; } if (!c.isRinging()) { Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!"); // This is a very strange case: an incoming call that stopped // ringing almost instantly after the onNewRingingConnection() // event. There's nothing we can do here, so just bail out // without doing anything. (But presumably we'll log it in // the call log when the disconnect event comes in...) return; } // Stop any signalInfo tone being played on receiving a Call stopSignalInfoTone(); Call.State state = c.getState(); // State will be either INCOMING or WAITING. if (VDBG) log("- connection is ringing! state = " + state); // if (DBG) PhoneUtils.dumpCallState(mPhone); // No need to do any service state checks here (like for // "emergency mode"), since in those states the SIM won't let // us get incoming connections in the first place. // TODO: Consider sending out a serialized broadcast Intent here // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the // ringer and going to the in-call UI. The intent should contain // the caller-id info for the current connection, and say whether // it would be a "call waiting" call or a regular ringing call. // If anybody consumed the broadcast, we'd bail out without // ringing or bringing up the in-call UI. // // This would give 3rd party apps a chance to listen for (and // intercept) new ringing connections. An app could reject the // incoming call by consuming the broadcast and doing nothing, or // it could "pick up" the call (without any action by the user!) // via some future TelephonyManager API. // // See bug 1312336 for more details. // We'd need to protect this with a new "intercept incoming calls" // system permission. // Obtain a partial wake lock to make sure the CPU doesn't go to // sleep before we finish bringing up the InCallScreen. // (This will be upgraded soon to a full wake lock; see // showIncomingCall().) if (VDBG) log("Holding wake lock on new incoming connection."); mApplication.requestWakeState(PhoneApp.WakeState.PARTIAL); // - don't ring for call waiting connections // - do this before showing the incoming call panel if (PhoneUtils.isRealIncomingCall(state)) { startIncomingCallQuery(c); } else { if (VDBG) log("- starting call waiting tone..."); if (mCallWaitingTonePlayer == null) { mCallWaitingTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING); mCallWaitingTonePlayer.start(); } // in this case, just fall through like before, and call // showIncomingCall(). if (DBG) log("- showing incoming call (this is a WAITING call)..."); showIncomingCall(); } // Note we *don't* post a status bar notification here, since // we're not necessarily ready to actually show the incoming call // to the user. (For calls in the INCOMING state, at least, we // still need to run a caller-id query, and we may not even ring // at all if the "send directly to voicemail" flag is set.) // // Instead, we update the notification (and potentially launch the // InCallScreen) from the showIncomingCall() method, which runs // when the caller-id query completes or times out. if (VDBG) log("- onNewRingingConnection() done."); }調用showIncomingCall();函數顯示來電界面private void showIncomingCall() { log("showIncomingCall()... phone state = " + mCM.getState()); // Before bringing up the "incoming call" UI, force any system // dialogs (like "recent tasks" or the power dialog) to close first. try { ActivityManagerNative.getDefault().closeSystemDialogs("call"); } catch (RemoteException e) { } mApplication.preventScreenOn(true); mApplication.requestWakeState(PhoneApp.WakeState.FULL); // Post the "incoming call" notification *and* include the // fullScreenIntent that'll launch the incoming-call UI. // (This will usually take us straight to the incoming call // screen, but if an immersive activity is running it'll just // appear as a notification.) if (DBG) log("- updating notification from showIncomingCall()..."); mApplication.notificationMgr.updateNotificationAndLaunchIncomingCallUi(); }NotificationMgr.javapublic void updateNotificationAndLaunchIncomingCallUi() { // Set allowFullScreenIntent=true to indicate that we *should* // launch the incoming call UI if necessary. updateInCallNotification(true); }private void updateInCallNotification(boolean allowFullScreenIntent) { // incoming call is ringing: if (hasRingingCall) { if (DBG) log("- Using hi-pri notification for ringing call!"); // This is a high-priority event that should be shown even if the // status bar is hidden or if an immersive activity is running. notification.flags |= Notification.FLAG_HIGH_PRIORITY; notification.tickerText = expandedViewLine2; if (allowFullScreenIntent) { if (DBG) log("- Setting fullScreenIntent: " + inCallPendingIntent); notification.fullScreenIntent = inCallPendingIntent; Call ringingCall = mCM.getFirstActiveRingingCall(); if ((ringingCall.getState() == Call.State.WAITING) && !mApp.isShowingCallScreen()) { Log.i(LOG_TAG, "updateInCallNotification: call-waiting! force relaunch..."); // Cancel the IN_CALL_NOTIFICATION immediately before // (re)posting it; this seems to force the // NotificationManager to launch the fullScreenIntent. mNotificationManager.cancel(IN_CALL_NOTIFICATION); } } } if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification); mNotificationManager.notify(IN_CALL_NOTIFICATION, notification); // Finally, refresh the mute and speakerphone notifications (since // some phone state changes can indirectly affect the mute and/or // speaker state). updateSpeakerNotification(); updateMuteNotification(); }PendingIntent inCallPendingIntent =
PendingIntent.getActivity(mContext, 0,
PhoneApp.createInCallIntent(), 0);
notification.contentIntent = inCallPendingIntent;
/* package */static Intent createInCallIntent() { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); intent.setClassName("com.android.phone", getCallScreenClassName()); return intent; }static String getCallScreenClassName() { return InCallScreen.class.getName(); }通過PendingIntent來啟動InCallScreen來電界面,接聽後就跟撥號界面一樣了。在測試android:persistent="true"時,編寫了一個測試程序,一定要安裝在system/app/目錄下,在開機時才會啟動,在程序啟動後不會被系統回收,非常簡單
開機後會打印在程序中添加的消息
sh-4.2# logcat -v time | grep PersionApp 01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate test 01-02 00:18:46.090 I/PersionApp( 1033): [Persion]----------------------------->Persion 01-02 00:18:46.090 I/PersionApp( 1033): [PersionApp]------------------->onCreate persion = null示例代碼:http://download.csdn.net/detail/deng0zhaotai/7714163
Android 動態菜單先上效果圖比較簡單,主要就是屬性動畫的使用和坐標角度的小細節。實現實現效果: 圖標按照路徑一路縮放漸變過來即可。核心代碼 /** * Item
來自:https://developer.android.com/guide/components/services.htmlService是一個可以在後台執行長時間運行
今天給大家分享下關於修改MTK平台下系統首次開機的默認屏幕背光亮度,系統語言默認英語情況下修改日期格式,修改拍照屬性,具體修改的地方不清楚或者沒人告知的話,請參照之前幾篇
本節引言: 前兩節我們學了Bitmap和一些基本的繪圖API的屬性以及常用的方法,但心裡總覺得有點 不踏實,總得寫點什麼加深下映像是吧,嗯,本節我們就來寫兩個