<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                installPackageWithVerification的代碼如下: **PackageManagerService.java::installPackageWithVerification函數** ~~~ public void installPackageWithVerification(UripackageURI, IPackageInstallObserverobserver, int flags, String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest) { //檢查客戶端進程是否具有安裝Package的權限。在本例中,該客戶端進程是shell mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INSTALL_PACKAGES,null); final int uid = Binder.getCallingUid(); final int filteredFlags; if(uid == Process.SHELL_UID || uid == 0) { ......//如果通過shell pm的方式安裝,則增加INSTALL_FROM_ADB標志 filteredFlags = flags | PackageManager.INSTALL_FROM_ADB; }else { filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB; } //創建一個Message,code為INIT_COPY,將該消息發送給之前在PKMS構造函數中 //創建的mHandler對象,將在另外一個工作線程中處理此消息 final Message msg = mHandler.obtainMessage(INIT_COPY); //創建一個InstallParams,其基類是HandlerParams msg.obj = new InstallParams(packageURI, observer, filteredFlags,installerPackageName, verificationURI,manifestDigest); mHandler.sendMessage(msg); } ~~~ installPackageWithVerification函數倒是蠻清閑,簡簡單單創建幾個對象,然后發送INIT_COPY消息給mHandler,就甩手退出了。根據之前在PKMS構造函數中介紹的知識可知,mHandler被綁定到另外一個工作線程(借助ThreadHandler對象的Looper)中,所以該INIT_COPY消息也將在那個工作線程中進行處理。我們馬上轉戰到那。 1. INIT_COPY處理 INIT_COPY只是安裝流程的第一步。先來看相關代碼: **PackageManagerService.java::handleMesssage** ~~~ public void handleMessage(Message msg) { try { doHandleMessage(msg);//調用doHandleMessage函數 } ...... } voiddoHandleMessage(Message msg) { switch(msg.what) { caseINIT_COPY: { //①這里記錄的是params的基類類型HandlerParams,實際類型為InstallParams HandlerParams params = (HandlerParams) msg.obj; //idx為當前等待處理的安裝請求的個數 intidx = mPendingInstalls.size(); if(!mBound) { /* 很多讀者可能想不到,APK的安裝居然需要使用另外一個APK提供的服務,該服務就是 DefaultContainerService,由DefaultCotainerService.apk提供, 下面的connectToService函數將調用bindService來啟動該服務 */ if(!connectToService()) { return; }else {//如果已經連上,則以idx為索引,將params保存到mPendingInstalls中 mPendingInstalls.add(idx, params); } } else { mPendingInstalls.add(idx, params); if(idx == 0) { //如果安裝請求隊列之前的狀態為空,則表明要啟動安裝 mHandler.sendEmptyMessage(MCS_BOUND); } } break; } ......//后續再分析 ~~~ 這里假設之前已經成功啟動了DefaultContainerService(以后簡稱DCS),并且idx為零,所以這是PKMS首次處理安裝請求,也就是說,下一個將要處理的是MCS_BOUND消息。 >[info] **注意**:connectToService在調用bindService時會傳遞一個DefaultContainerConnection類型的對象,以接收服務啟動的結果。當該服務成功啟動后,此對象的onServiceConnected被調用,其內部也將發送MCS_BOUND消息給mHandler。 2. MCS_BOUND處理 現在,安裝請求的狀態從INIT_COPY變成MCS_BOUND了,此時的處理流程時怎樣的呢?依然在doHandleMessage函數中,直接從對應的case開始,代碼如下: **PackageManagerService.java::** ~~~ ......//接doHandleMesage中的switch/case case MCS_BOUND: { if(msg.obj != null) { mContainerService= (IMediaContainerService) msg.obj; } if(mContainerService == null) { ......//如果沒法啟動該service,則不能安裝程序 mPendingInstalls.clear(); } else if(mPendingInstalls.size() > 0) { HandlerParamsparams = mPendingInstalls.get(0); if(params != null) { //調用params對象的startCopy函數,該函數由基類HandlerParams定義 if(params.startCopy()) { ...... if(mPendingInstalls.size() > 0) { mPendingInstalls.remove(0);//刪除隊列頭 } if (mPendingInstalls.size() == 0) { if (mBound) { ......//如果安裝請求都處理完了,則需要和Service斷絕聯系, //通過發送MSC_UNB消息處理斷交請求。讀者可自行研究此情況的處理流程 removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); sendMessageDelayed(ubmsg, 10000); } }else { //如果還有未處理的請求,則繼續發送MCS_BOUND消息。 //為什么不通過一個循環來處理所有請求呢 mHandler.sendEmptyMessage(MCS_BOUND); } } } ...... break; ~~~ MCS_BOUND的處理還算簡單,就是調用HandlerParams的startCopy函數。在深入分析前,應先認識一下HandlerParams及相關的對象。 (1) HandlerParams和InstallArgs介紹 除了HandlerParams家族外,這里提前請出另外一個家族InstallArgs及其成員,如圖4-8所示。 :-: ![](http://img.blog.csdn.net/20150803110954000?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-8 HandlerParams及InstallArgs家族成員 由圖4-8可知: - HandlerParams和InstallArgs均為抽象類。 - HandlerParams有三個子類,分別是InstallParams、MoveParams和MeasureParams。其中,InstallParams用于處理APK的安裝,MoveParams用于處理某個已安裝APK的搬家請求(例如從內部存儲移動到SD卡上),MeasureParams用于查詢某個已安裝的APK占據存儲空間的大小(例如在設置程序中得到的某個APK使用的緩存文件的大小)。 - 對于InstallParams來說,它還有兩個伴兒,即InstallArgs的派生類FileInstallArgs和SdInstallArgs。其中,FileInstallArgs針對的是安裝在內部存儲的APK,而SdInstallArgs針對的是那些安裝在SD卡上的APK。 本節將討論用于內部存儲安裝的FileInstallArgs。 * * * * * **提示**:讀者可以在介紹完MountService后,結合本章知識點,自行研究SdInstallArgs的處理流程。 * * * * * 在前面MCS_BOUND的處理中,首先調用InstallParams的startCopy函數,該函數由其基類HandlerParams實現,代碼如下: **PackageManagerService.java::HandlerParams.startCopy函數** ~~~ final boolean startCopy() { booleanres; try { //MAX_RETIRES目前為4,表示嘗試4次安裝,如果還不成功,則認為安裝失敗 if(++mRetries > MAX_RETRIES) { mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy();//①調用派生類的handleStartCopy函數 res= true; } } ...... handleReturnCode();//②調用派生類的handleReturnCode,返回處理結果 returnres; } ~~~ 在上述代碼中,基類的startCopy將調用子類實現的handleStartCopy和handleReturnCode函數。下面來看InstallParams是如何實現這兩個函數的。 (2) InstallParams分析 先來看派生類InstallParams的handleStartCopy函數,代碼如下: **PackageManagerService::InstallParams.handleStartCopy** ~~~ public void handleStartCopy() throwsRemoteException { int ret= PackageManager.INSTALL_SUCCEEDED; finalboolean fwdLocked = //本書不考慮fwdLocked的情況 (flags &PackageManager.INSTALL_FORWARD_LOCK) != 0; //根據adb install的參數,判斷安裝位置 finalboolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; finalboolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0; PackageInfoLite pkgLite = null; if(onInt && onSd) { //APK不能同時安裝在內部存儲和SD卡上 ret =PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } elseif (fwdLocked && onSd) { //fwdLocked的應用不能安裝在SD卡上 ret =PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { finallong lowThreshold; //獲取DeviceStorageMonitorService的binder客戶端 finalDeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService( DeviceStorageMonitorService.SERVICE); if(dsm == null) { lowThreshold = 0L; }else { //從DSMS查詢內部空間最小余量,默認是總空間的10% lowThreshold = dsm.getMemoryLowThreshold(); } try { //授權DefContainerService URI讀權限 mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION); //①調用DCS的getMinimalPackageInfo函數,得到一個PackageLite對象 pkgLite =mContainerService.getMinimalPackageInfo(packageURI, flags,lowThreshold); }finally ......//撤銷URI授權 //PacakgeLite的recommendedInstallLocation成員變量保存該APK推薦的安裝路徑 int loc =pkgLite.recommendedInstallLocation; if (loc== PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) { ret= PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else if......{ } else { //②根據DCS返回的安裝路徑,還需要調用installLocationPolicy進行檢查 loc =installLocationPolicy(pkgLite, flags); if(!onSd && !onInt) { if(loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { flags |= PackageManager.INSTALL_EXTERNAL; flags &=~PackageManager.INSTALL_INTERNAL; } ......//處理安裝位置為內部存儲的情況 } } } //③創建一個安裝參數對象,對于安裝位置為內部存儲的情況,args的真實類型為FileInstallArgs finalInstallArgs args = createInstallArgs(this); mArgs =args; if (ret== PackageManager.INSTALL_SUCCEEDED) { final int requiredUid = mRequiredVerifierPackage == null ? -1 :getPackageUid(mRequiredVerifierPackage); if(requiredUid != -1 && isVerificationEnabled()) { ......//④待會再討論verification的處理 }else { //⑤調用args的copyApk函數 ret= args.copyApk(mContainerService, true); } } mRet =ret;//確定返回值 } ~~~ 在以上代碼中,一共列出了五個關鍵點,總結如下: - 調用DCS的getMinimalPackageInfo函數,將得到一個PackageLite對象,該對象是一個輕量級的用于描述APK的結構(相比PackageParser.Package來說)。在這段代碼邏輯中,主要想取得其recommendedInstallLocation的值。此值表示該APK推薦的安裝路徑。 - 調用installLocationPolicy檢查推薦的安裝路徑。例如系統Package不允許安裝在SD卡上。 - createInstallArgs將根據安裝位置創建不同的InstallArgs。如果是內部存儲,則返回FileInstallArgs,否則為SdInstallArgs。 - 在正式安裝前,應先對該APK進行必要的檢查。這部分代碼后續再介紹。 - 調用InstallArgs的copyApk。對本例來說,將調用FileInstallArgs的copyApk函數。 下面圍繞這五個基本關鍵點展開分析,其中installLocationPolicy和createInstallArgs比較簡單,讀者可自行研究。 3. handleStartCopy分析 (1) DefaultContainerService分析 首先分析DCS的getMinimalPackageInfo函數,其代碼如下: **DefaultContainerService.java::getMinimalPackageInfo函數** ~~~ public PackageInfoLite getMinimalPackageInfo(finalUri fileUri, int flags, longthreshold) { //注意該函數的參數:fileUri指向該APK的文件路徑(此時還在/data/local/tmp下) PackageInfoLite ret = new PackageInfoLite(); ...... Stringscheme = fileUri.getScheme(); ...... StringarchiveFilePath = fileUri.getPath(); DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); //調用PackageParser的parsePackageLite解析該APK文件 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath,0); if (pkg== null) {//解析失敗 ......//設置錯誤值 returnret; } ret.packageName = pkg.packageName; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; //調用recommendAppInstallLocation,取得一個合理的安裝位置 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,archiveFilePath, flags, threshold); returnret; } ~~~ APK可在AndroidManifest.xml中聲明一個安裝位置,不過DCS除了解析該位置外,還需要做進一步檢查,這個工作由recommendAppInstallLocation函數完成,代碼如下: **DefaultContainerService.java::recommendAppInstallLocation函數** ~~~ private int recommendAppInstallLocation(intinstallLocation, StringarchiveFilePath, int flags,long threshold) { int prefer; booleancheckBoth = false; check_inner: { if((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { prefer = PREFER_INTERNAL; break check_inner; //根據FOWRAD_LOCK的情況,只能安裝在內部存儲 } elseif ((flags & PackageManager.INSTALL_INTERNAL) != 0) { prefer = PREFER_INTERNAL; break check_inner; } ......//檢查各種情況 } else if(installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { prefer= PREFER_INTERNAL;//一般設定的位置為AUTO,默認是內部空間 checkBoth = true; //設置checkBoth為true breakcheck_inner; } //查詢settings數據庫中的secure表,獲取用戶設置的安裝路徑 intinstallPreference = Settings.System.getInt(getApplicationContext() .getContentResolver(), Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); if(installPreference == PackageHelper.APP_INSTALL_INTERNAL) { prefer = PREFER_INTERNAL; break check_inner; } else if(installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { prefer= PREFER_EXTERNAL; breakcheck_inner; } prefer =PREFER_INTERNAL; } //判斷外部存儲空間是否為模擬的,這部分內容我們以后再介紹 finalboolean emulated = Environment.isExternalStorageEmulated(); final FileapkFile = new File(archiveFilePath); booleanfitsOnInternal = false; if(checkBoth || prefer == PREFER_INTERNAL) { try {//檢查內部存儲空間是否足夠大 fitsOnInternal = isUnderInternalThreshold(apkFile, threshold); } ...... } booleanfitsOnSd = false; if(!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { try{ //檢查外部存儲空間是否足夠大 fitsOnSd = isUnderExternalThreshold(apkFile); } ...... } if (prefer== PREFER_INTERNAL) { if(fitsOnInternal) {//返回推薦安裝路徑為內部空間 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; } } elseif (!emulated && prefer == PREFER_EXTERNAL) { if(fitsOnSd) {//返回推薦安裝路徑為外部空間 returnPackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } if(checkBoth) { if(fitsOnInternal) {//如果內部存儲滿足條件,先返回內部空間 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; }else if (!emulated && fitsOnSd) { return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } ...... //到此,前幾個條件都不滿足,此處將根據情況返回一個明確的錯誤值 returnPackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; } } ~~~ DCS的getMinimalPackageInfo函數為了得到一個推薦的安裝路徑做了不少工作,其中,各種安裝策略交叉影響。這里總結一下相關的知識點: - APK在AndroidManifest.xml中設置的安裝點默認為AUTO,在具體對應時傾向內部空間。 - 用戶在Settings數據庫中設置的安裝位置。 - 檢查外部存儲或內部存儲是否有足夠空間。 (2) InstallArgs的copyApk函數分析 至此,我們已經得到了一個合適的安裝位置(先略過Verification這一步)。下一步工作就由copyApk來完成。根據函數名可知該函數將完成APK文件的復制工作,此中會有蹊蹺嗎?來看下面的代碼。 **PackageManagerService.java::InstallArgs.copyApk函數** ~~~ int copyApk(IMediaContainerService imcs, booleantemp) throws RemoteException { if (temp){ /* 本例中temp參數為true,createCopyFile將在/data/app下創建一個臨時文件。 臨時文件名為vmdl-隨機數.tmp。為什么會用這樣的文件名呢? 因為PKMS通過Linux的inotify機制監控了/data/app,目錄,如果新復制生成的文件名后綴 為apk,將觸發PKMS掃描。為了防止發生這種情況,這里復制生成的文件才有了 如此奇怪的名字 */ createCopyFile(); } FilecodeFile = new File(codeFileName); ...... ParcelFileDescriptor out = null; try { out =ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE); }...... int ret= PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION); //調用DCS的copyResource,該函數將執行復制操作,最終結果是/data/local/tmp //下的APK文件被復制到/data/app下,文件名也被換成vmdl-隨機數.tmp ret= imcs.copyResource(packageURI, out); }finally { ......//關閉out,撤銷URI授權 } returnret; } ~~~ 關于臨時文件,這里提供一個示例,如圖4-9所示。 :-: ![](http://img.blog.csdn.net/20150803111034657?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 圖4-9 createCopyFile生成的臨時文件 由圖4-9可知:/data/app下有兩個文件,第一個是正常的APK文件,第二個是createCopyFile生成的臨時文件。 4. handleReturnCode分析 在HandlerParams的startCopy函數中,handleStartCopy執行完之后,將調用handleReturnCode開展后續工作,代碼如下: **PackageManagerService.java::InstallParams.HandleParams** void handleReturnCode() { if(mArgs != null) { //調用processPendingInstall函數,mArgs指向之前創建的FileInstallArgs對象 processPendingInstall(mArgs, mRet); } } **PackageManagerService.java::** ~~~ private void processPendingInstall(finalInstallArgs args, final intcurrentStatus) { //向mHandler中拋一個Runnable對象 mHandler.post(new Runnable() { publicvoid run() { mHandler.removeCallbacks(this); //創建一個PackageInstalledInfo對象, PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) { //①調用FileInstallArgs的doPreInstall args.doPreInstall(res.returnCode); synchronized (mInstallLock) { //②調用installPackageLI進行安裝 installPackageLI(args, true, res); } //③調用FileInstallArgs的doPostInstall args.doPostInstall(res.returnCode); } final boolean update = res.removedInfo.removedPackage != null; boolean doRestore = (!update&& res.pkg != null && res.pkg.applicationInfo.backupAgentName!= null); int token;//計算一個ID號 if(mNextInstallToken < 0) mNextInstallToken = 1; token = mNextInstallToken++; //創建一個PostInstallData對象 PostInstallData data = new PostInstallData(args, res); //保存到mRunningInstalls結構中,以token為key mRunningInstalls.put(token, data); if (res.returnCode ==PackageManager.INSTALL_SUCCEEDED && doRestore) { ......//備份恢復的情況暫時不考慮 } if(!doRestore) { //④拋一個POST_INSTALL消息給mHandler進行處理 Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } }); } ~~~ 由上面代碼可知,handleReturnCode主要做了4件事情: - 調用InstallArgs的doPreInstall函數,在本例中是FileInstallArgs的doPreInstall函數。 - 調用PKMS的installPackageLI函數進行APK安裝,該函數內部將調用InstallArgs的doRename對臨時文件進行改名。另外,還需要掃描此APK文件。此過程和之前介紹的“掃描系統Package”一節的內容類似。至此,該APK中的私有財產就全部被登記到PKMS內部進行保存了。 - 調用InstallArgs的doPostInstall函數,在本例中是FileInstallArgs的doPostInstall函數。 - 此時,該APK已經安裝完成(不論失敗還是成功),繼續向mHandler拋送一個POST_INSTALL消息,該消息攜帶一個token,通過它可從mRunningInstalls數組中取得一個PostInstallData對象。 * * * * * **提示**:對于FileInstallArgs來說,其doPreInstall和doPostInstall都比較簡單,讀者可自行閱讀相關代碼。另外,讀者也可自行研究PKMS的installPackageLI函數。 * * * * * 這里介紹一下FileInstallArgs的doRename函數,它的功能是將臨時文件改名,最終的文件的名稱一般為“包名-數字.apk”。其中,數字是一個index,從1開始。讀者可參考圖4-9中/data/app目錄下第一個文件的文件名。 5. POST_INSTALL處理 現在需要處理POST_INSTALL消息,因為adb install還等著安裝結果呢。相關代碼如下: **PackageManagerService.java::doHandleMessage函數** ~~~ ......//接前面的switch/case case POST_INSTALL: { PostInstallData data = mRunningInstalls.get(msg.arg1); mRunningInstalls.delete(msg.arg1); booleandeleteOld = false; if (data!= null) { InstallArgs args = data.args; PackageInstalledInfo res = data.res; if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) { res.removedInfo.sendBroadcast(false, true); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } //發送PACKAGE_ADDED廣播 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName,extras, null, null); if (update) { /* 如果是APK升級,那么發送PACKAGE_REPLACE和MY_PACKAGE_REPLACED廣播。 二者不同之處在于PACKAGE_REPLACE將攜帶一個extra信息 */ } Runtime.getRuntime().gc(); if(deleteOld) { synchronized (mInstallLock) { //調用FileInstallArgs的doPostDeleteLI進行資源清理 res.removedInfo.args.doPostDeleteLI(true); } } if(args.observer != null) { try { // 向pm通知安裝的結果 args.observer.packageInstalled(res.name, res.returnCode); } ...... } break; ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看