Android結合源碼分析Power按鍵處理流程


    這是之前團隊進行技術交流時,我選擇的一個主題,那段時間解決power鎖屏按鍵的bug,搞得頭大,所以借此機會結合Android8.0源碼去分析Power鍵的處理流程,也將此分享出來,希望對大家有所幫助,本文為博主原創文章,有不對的地方,歡迎大家指正!

作者: Android之路

出處: https://www.cnblogs.com/sparrowlhl/p/11174488.html

版權聲明:本文為博主原創文章,轉載請注明出處,謝謝!

    Android系統中,一般的按鍵都可以在應用中處理,但是,對於系統級別的按鍵上層應用是無法收到消息的,也就是說,你的APP是無法直接處理的。針對這種系統級的按鍵事件,都是在Event事件分發前處理。Event事件分發后,只有包含有Activity的APP才能處理事件;若APP無法處理,則需要在PhoneWindowManager中處理。

    本文所講的Power鍵則屬於該種情況。即用戶觸發Power鍵,底層收到按鍵會回調InputMonitor的函數dispatchUnhandledKey()。

一、為何最終處理者是PhoneWindowManager?

    通過上文可知最終事件的處理是由PhoneWindowManager完成的;那么,按鍵后,系統是如何傳遞到PhoneWindowManager?下面就從源碼的角度分析一下該過程。

    • WindowManagerService:Framework 最核心的服務之一,負責窗口管理。
    • InputManagerService:輸入管理服務。

    上述兩個服務與Power按鍵相關,但是兩者是如何關聯的,就要從它們的創建說起.我們都知道,Android系統中的核心進程是system_server,對應SystemServer類,在其run()方法中會啟動一堆的service,當然包括上述兩個服務。具體源碼分析如下:

    1、先創建inputManager,再創建WindowManagerService對象時,可發現作為參數引用了上述inputManager,且創建了PhoneWindowManager實例:

源碼路徑:frameworks/base/services/java/com/android/server/SystemServer.java

private
void startOtherServices() { ...... inputManager = new InputManagerService(context); //輸入系統服務 【step_SystemServer_1】 ......     //【step_SystemServer_2】 wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); //PhoneWindowManager實例 ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); }
   
源碼路徑:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
            ......

             mPolicy = policy;    //實例: PhoneWindowManager對象      【step_InputMonitor_2】
       ......
}

    2、啟動inputManager之前,設置了一個回調接口:

      //消息分發之前回調--->查看InputManagerService
      inputManager.setWindowManagerCallbacks(wm.getInputMonitor());       inputManager.start();

    3、InputMonitor.java:【底層是C/C++相關的,博主對C也不太了解,此處就不作分析了】    

    底層收到按鍵會回調InputManagerService的dispatchUnhandledKey()--->InputMonitor的函數dispatchUnhandledKey()。具體由底層InputDispatcher.cpp調用。

源碼路徑: frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java   //【Power鍵屬於系統級按鍵,因此處理方法是dispatchUnhandledKey】
    /* Provides an opportunity for the window manager policy to process a key that * the application did not handle. */ @Override public KeyEvent dispatchUnhandledKey( InputWindowHandle focus, KeyEvent event, int policyFlags) { WindowState windowState = focus != null ? (WindowState) focus.windowState : null; //此處 mservice: WindowManagerService 【step_InputMonitor_0】
        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags); }

    由這三步可知,最終由PhoneWindowManager處理。將上述整理成時序圖:

二、Power按鍵觸發后的具體執行邏輯分析.

列出幾種常見的觸發Power鍵的情況: 情況一:長按Power鍵
情況二:單獨短按Power鍵 情況三:Power
+ 音量鍵(-)
以下也以這三種情況結合源碼分析流程。

    由上文可知,真正的處理邏輯在PhoneWindowManager類中,該類有兩個方法:interceptKeyBeforeDispatching和interceptKeyBeforeQueueing,包括了幾乎所有按鍵的處理。

    •   interceptKeyBeforeDispatching:主要處理Home鍵、Menu鍵、Search鍵等。
    •   interceptKeyBeforeQueueing:主要處理音量鍵、電源鍵(Power鍵)、耳機鍵等。

    當前Power鍵處理流程:

    dispatchUnhandledKey()------>interceptFallback()---->interceptKeyBeforeQueueing()

    下面從interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)分析。

    而一個按鍵包含兩個動作Down和UP,因此從這兩個方面分析interceptKeyBeforeQueueing()的執行流程。

    • 按下:  interceptPowerKeyDown(KeyEvent event, boolean interactive)
    • 釋放:  interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled)

    參數含義:    

          interactive:是否亮屏

            KeyEvent.FLAG_FALLBACK:不被應用處理的按鍵事件或一些在  鍵值映射中不被處理的事件(例:軌跡球事件等)。

    根據操作按鍵時系統是否亮屏,代碼執行的邏輯也不同,因此每個事件下分別從亮滅屏來分析,具體如下:

    (下述代碼的執行過程中有對一些變量的判斷,而這些值都是系統配置的,在config.xml中,因此具體執行哪個流程以當前平台配置為准)    

涉及到的配置信息的相關源碼路徑:
frameworks/base/core/java/android/view/ViewConfiguration.java
frameworks/base/core/res/res/values/config.xml

    1、按下(ACTION_DOWN):先上源碼PhoneWindowManager.java中的interceptPowerKeyDown函數。

  1  private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
  2         //FACE_UNLOCK_SUPPORT start
  3         Slog.i("FaceUnlockUtil", "interceptPowerKeyDown interactive = " + interactive);
  4         Settings.System.putInt(mContext.getContentResolver(), "faceunlock_start", 1);
  5         //FACE_UNLOCK_SUPPORT end
  6         // Hold a wake lock until the power key is released.
  7         if (!mPowerKeyWakeLock.isHeld()) {
  8             mPowerKeyWakeLock.acquire();          //獲得喚醒鎖
  9         }
 10 
 11         // Cancel multi-press detection timeout.
 12         if (mPowerKeyPressCounter != 0) {
 13             mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
 14         }
 15 
 16         // Detect user pressing the power button in panic when an application has
 17         // taken over the whole screen.
 18         boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
 19                 SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
 20                 isNavBarEmpty(mLastSystemUiFlags));
 21         if (panic) {
 22             mHandler.post(mHiddenNavPanic);
 23         }
 24 
 25         // Latch power key state to detect screenshot chord.
 26         if (interactive && !mScreenshotChordPowerKeyTriggered
 27                 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {   
 28             mScreenshotChordPowerKeyTriggered = true;                //標記按下power key,用於組合鍵截屏,具體參考下述3
 29             mScreenshotChordPowerKeyTime = event.getDownTime();
 30             interceptScreenshotChord();
 31         }
 32 
 33         // Stop ringing or end call if configured to do so when power is pressed.
 34         TelecomManager telecomManager = getTelecommService();
 35         boolean hungUp = false;
 36         if (telecomManager != null) {
 37             if (telecomManager.isRinging()) {
 38                 // Pressing Power while there's a ringing incoming
 39                 // call should silence the ringer.
 40                 telecomManager.silenceRinger();
 41             } else if ((mIncallPowerBehavior
 42                     & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
 43                     && telecomManager.isInCall() && interactive) {
 44                 // Otherwise, if "Power button ends call" is enabled,
 45                 // the Power button will hang up any current active call.
 46                 hungUp = telecomManager.endCall();
 47             }
 48         }
 49 
 50         GestureLauncherService gestureService = LocalServices.getService(
 51                 GestureLauncherService.class);
 52         boolean gesturedServiceIntercepted = false;
 53         if (gestureService != null) {
 54             gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
 55                     mTmpBoolean);
 56             if (mTmpBoolean.value && mRequestedOrGoingToSleep) {
 57                 mCameraGestureTriggeredDuringGoingToSleep = true;
 58             }
 59         }
 60 
 61         // Inform the StatusBar; but do not allow it to consume the event.
 62         sendSystemKeyToStatusBarAsync(event.getKeyCode());     //
 63 
 64         // If the power key has still not yet been handled, then detect short
 65         // press, long press, or multi press and decide what to do.
 66         mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
 67                 || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
 68         if (!mPowerKeyHandled) {
 69             if (interactive) {   //亮屏
 70                 // When interactive, we're already awake.
 71                 // Wait for a long press or for the button to be released to decide what to do.
 72                 if (hasLongPressOnPowerBehavior()) { //長按----判斷是否為彈出操作界面的邏輯
 73                     Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
 74                     msg.setAsynchronous(true);
 75                     mHandler.sendMessageDelayed(msg,
 76                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());  //500ms
 77                 }
 78             } else {
 79                 wakeUpFromPowerKey(event.getDownTime());     //喚醒屏幕---......
 80 
 81                 if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { //當前配置mSupportLongPressPowerWhenNonInteractive=false
 82                     Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
 83                     msg.setAsynchronous(true);
 84                     mHandler.sendMessageDelayed(msg,
 85                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
 86                     mBeganFromNonInteractive = true;
 87                 } else {
 88                     final int maxCount = getMaxMultiPressPowerCount();   //當前配置不支持config.xml
 89 
 90                     if (maxCount <= 1) {
 91                         mPowerKeyHandled = true;      //執行此處#
 92                     } else {
 93                         mBeganFromNonInteractive = true;
 94                     }
 95                 }
 96             }
 97         }
 98     }
 99 
100 ......
101 
102     private int getResolvedLongPressOnPowerBehavior() {
103         if (FactoryTest.isLongPressOnPowerOffEnabled()) { //默認false,
104             return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;    //長按Power,直接關機;對應屬性:factory.long_press_power_off
105         }
106         return mLongPressOnPowerBehavior;
107     }
108 
109     private boolean hasLongPressOnPowerBehavior() {
110         return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
111     }

    根據上述代碼分析:

    (1)亮屏:第69-76行代碼。

      hasLongPressOnPowerBehavior()---mHanlder發送消息(500ms)--powerLongPress() ------>getResolvedLongPressOnPowerBehavior()根據獲取的值來 執行相應的流程:    

    • LONG_PRESS_POWER_GLOBAL_ACTIONS:彈出操作界面---->showGlobalActionsInternal()--->sendCloseSystemWindows(String reason) /mGlobalActions.showDialog()---->PhoneWindow.sendCloseSystemWindows(mContext, reason)....
    • LONG_PRESS_POWER_SHUT_OFF/LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:直接關機,相當於點擊上述彈框中的關機操作。此處可結合源碼查看:  對應屬性:factory.long_press_power_off  使用命令:

          adb shell getprop/setprop...即可測試效果。

    (2)滅屏:第79-96行代碼。

      wakeUpFromPowerKey()---->wakeUp()----  >mPowerManager.wakeUp()....調用PowerManagerService喚 醒屏幕.

 1     private void wakeUpFromPowerKey(long eventTime) {
 2         wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, "android.policy:POWER");
 3     }
 4 
 5     private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) {
 6         ......
10         final boolean theaterModeEnabled = isTheaterModeEnabled();
11         if (!wakeInTheaterMode && theaterModeEnabled) {
12             return false;
13         }
14 
15         // Settings.Global.THEATER_MODE_ON:
16         if (theaterModeEnabled) {
17             Settings.Global.putInt(mContext.getContentResolver(),
18                     Settings.Global.THEATER_MODE_ON, 0);
19         }
20 
21  mPowerManager.wakeUp(wakeTime, reason);
22         return true;
23     }

    2、釋放(ACTION_UP):

 1 private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
 2         final boolean handled = canceled || mPowerKeyHandled;  //在interceptPowerKeyDown()中滅屏或長按 對應#上述interceptPowerKeyDown(...)代碼中的第91行 mPowerKeyHandled=true
 3         mScreenshotChordPowerKeyTriggered = false;
 4         cancelPendingScreenshotChordAction();
 5         cancelPendingPowerKeyAction();   //取消長按事件---即500ms內未監聽到釋放,才執行長按事件
 6 
 7         if (!handled) { //亮屏 短按Power釋放時執行此處##  因mPowerKeyHandled=false
 8             // Figure out how to handle the key now that it has been released.
 9             mPowerKeyPressCounter += 1;
10 
11             final int maxCount = getMaxMultiPressPowerCount();  //maxCount=1
12             final long eventTime = event.getDownTime();
13             if (mPowerKeyPressCounter < maxCount) {  //不成立
14                 // This could be a multi-press.  Wait a little bit longer to confirm.
15                 // Continue holding the wake lock.
16                 Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
17                         interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
18                 msg.setAsynchronous(true);
19                 mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
20                 return;
21             }
22 
23             // No other actions.  Handle it immediately.
24             powerPress(eventTime, interactive, mPowerKeyPressCounter);    //mPowerKeyPressCounter=1
25         }
26 
27         // Done.  Reset our state.
28         finishPowerKeyPress();   
29     }
30 
31     private void finishPowerKeyPress() {
32         mBeganFromNonInteractive = false;
33         mPowerKeyPressCounter = 0;
34         if (mPowerKeyWakeLock.isHeld()) {
35             mPowerKeyWakeLock.release();    //釋放down事件時獲得的鎖
36         }
37     }
38 ......
39 
40     private void powerPress(long eventTime, boolean interactive, int count) {
41         if (mScreenOnEarly && !mScreenOnFully) {
42             Slog.i(TAG, "Suppressed redundant power key press while "
43                     + "already in the process of turning the screen on.");
44             return;
45         }
46 
47         if (count == 2) {
48             powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
49         } else if (count == 3) {
50             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
51         } else if (interactive && !mBeganFromNonInteractive) {   //  mBeganFromNonInteractive=false
52             switch (mShortPressOnPowerBehavior) {  //mShortPressOnPowerBehavior : 配置為1
53                 case SHORT_PRESS_POWER_NOTHING:
54                     break;
55                 case SHORT_PRESS_POWER_GO_TO_SLEEP: //執行#
56                     goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
57                     break;
58           .......
59       }
60 } 
65

    (1)亮屏:       

      powerPress()--->goToSleep()--- >mPowerManager.goToSleep()...調用PowerManagerService 使系統睡眠。

    (2)滅屏:

      finishPowerKeyPress()...

    3、組合鍵(Power + 音量減):功能就是我們常用的屏幕截圖的快捷方式。

  1     @Override
  2     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
  3         if (!mSystemBooted) {
  4             // If we have not yet booted, don't let key events do anything.
  5             return 0;
  6         }
  7         
  8         ......//代碼略
  9         final int keyCode = event.getKeyCode();
 10         // Basic policy based on interactive state.
 11         int result;
 12         
 13         // Handle special keys.
 14         switch (keyCode) {
 15             ......
 16             case KeyEvent.KEYCODE_VOLUME_MUTE: {
 17                 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {   //音量鍵減-     【Power + VOLUME_DOWN】截屏操作
 18                     if (down) {
 19                         if (interactive && !mScreenshotChordVolumeDownKeyTriggered
 20                                 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
 21                             mScreenshotChordVolumeDownKeyTriggered = true;
 22                             mScreenshotChordVolumeDownKeyTime = event.getDownTime();
 23                             mScreenshotChordVolumeDownKeyConsumed = false;
 24                             cancelPendingPowerKeyAction();
 25                             interceptScreenshotChord();
 26                             interceptAccessibilityShortcutChord();
 27                         }
 28                     } else {
 29                         mScreenshotChordVolumeDownKeyTriggered = false;
 30                         cancelPendingScreenshotChordAction();
 31                         cancelPendingAccessibilityShortcutAction();
 32                     }
 33                 } else if(...){
 34                     ......
 35                 }
 36                 ......
 37                 break;    
 38             }
 39             case KeyEvent.KEYCODE_POWER: {      //POWER 鍵
 40                 if(SystemProperties.getBoolean("sys.requireKey", false)) break;
 41                 // Any activity on the power button stops the accessibility shortcut
 42                 cancelPendingAccessibilityShortcutAction();
 43                 result &= ~ACTION_PASS_TO_USER;        //不分發按鍵至應用
 44                 isWakeKey = false; // wake-up will be handled separately
 45                 if (down) {
 46                     interceptPowerKeyDown(event, interactive);   //在上述1中interceptPowerKeyDown()的第28行可見標記mScreenshotChordPowerKeyTriggered = true;  47                 } else {
 48                     interceptPowerKeyUp(event, interactive, canceled);
 49                 }
 50                 break;
 51             }
 52             ......
 53         }
 54         
 55         ......
 56         return result;    
 57     }
 58     
 59     //截屏
 60     private void interceptScreenshotChord() {
 61         if (mScreenshotChordEnabled
 62                 && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered    //同時按下Volum_down + Power, 后執行的該方法
 63                 && !mA11yShortcutChordVolumeUpKeyTriggered) {
 64             final long now = SystemClock.uptimeMillis();
 65             if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
 66                     && now <= mScreenshotChordPowerKeyTime
 67                             + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {   //150ms
 68                 mScreenshotChordVolumeDownKeyConsumed = true;
 69                 cancelPendingPowerKeyAction();
 70  mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
 71                 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());   //根據是否在鎖屏界面設置不同的延遲時間,了解即可
 72             }
 73         }
 74     }
 75     
 76     private class ScreenshotRunnable implements Runnable {
 77         private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
 78 
 79         public void setScreenshotType(int screenshotType) {
 80             mScreenshotType = screenshotType;
 81         }
 82 
 83         @Override
 84         public void run() {
 85             takeScreenshot(mScreenshotType); //#  86         }
 87     }
 88     
 89     private static final String SYSUI_PACKAGE = "com.android.systemui";
 90     private static final String SYSUI_SCREENSHOT_SERVICE =
 91             "com.android.systemui.screenshot.TakeScreenshotService";
 92     
 93     private void takeScreenshot(final int screenshotType) {
 94         synchronized (mScreenshotLock) {
 95             if (mScreenshotConnection != null) {
 96                 return;
 97             }
 98             final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
 99                     SYSUI_SCREENSHOT_SERVICE);  //SystemUI中的截屏服務
100             final Intent serviceIntent = new Intent();
101             serviceIntent.setComponent(serviceComponent);
102             ServiceConnection conn = new ServiceConnection() {
103                 @Override
104                 public void onServiceConnected(ComponentName name, IBinder service) {
105                     synchronized (mScreenshotLock) {
106                         if (mScreenshotConnection != this) {
107                             return;
108                         }
109                         Messenger messenger = new Messenger(service);
110                         Message msg = Message.obtain(null, screenshotType);
111                         final ServiceConnection myConn = this;
112                         Handler h = new Handler(mHandler.getLooper()) {
113                             @Override
114                             public void handleMessage(Message msg) {
115                                 synchronized (mScreenshotLock) {
116                                     if (mScreenshotConnection == myConn) {
117                                         mContext.unbindService(mScreenshotConnection);
118                                         mScreenshotConnection = null;
119                                         mHandler.removeCallbacks(mScreenshotTimeout);
120                                     }
121                                 }
122                             }
123                         };
124                         msg.replyTo = new Messenger(h);
125                         msg.arg1 = msg.arg2 = 0;
126                         if (mStatusBar != null && mStatusBar.isVisibleLw())
127                             msg.arg1 = 1;
128                         if (mNavigationBar != null && mNavigationBar.isVisibleLw())
129                             msg.arg2 = 1;
130                         try {
131                             messenger.send(msg);
132                         } catch (RemoteException e) {
133                         }
134                     }
135                 }
136 
137                 @Override
138                 public void onServiceDisconnected(ComponentName name) {
139                     synchronized (mScreenshotLock) {
140                         if (mScreenshotConnection != null) {
141                             mContext.unbindService(mScreenshotConnection);
142                             mScreenshotConnection = null;
143                             mHandler.removeCallbacks(mScreenshotTimeout);
144                             notifyScreenshotError();
145                         }
146                     }
147                 }
148             };
149             if (mContext.bindServiceAsUser(serviceIntent, conn,
150                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
151                     UserHandle.CURRENT)) {
152                 mScreenshotConnection = conn;
153                 mHandler.postDelayed(mScreenshotTimeout, 10000);
154             }
155         }
156     }     

    結合上述代碼,可概括流程為:    

    interceptScreenshotChord():----->啟動線程ScreenshotRunnable---->takeScreenshot()----可看出真正的截圖操作是在SystemUI中.(感興趣的話,可自行研究)。

    將上述代碼執行細節整理出下述流程圖:(在線作圖:https://www.processon.com/)查看原圖

三、總結:

    由上文可知,Android系統響應Power鍵的情況可總述為以下內容:

    按下Down:

      (1)亮屏長按----彈出操作框(關機、重啟.....)

      (2)滅屏短按----wakeUp喚醒屏幕

    釋放Up:

      (1)亮屏長按不處理

      (2)滅屏不處理

      (3)亮屏短按----gotoSleep

    暫時就介紹到這里吧,若有什么不對的地方,歡迎交流指正,一起學習進步!  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM