Android10.0 鎖屏分析——KeyguardPatternView圖案鎖分析

學(xué)習(xí)筆記:

首先一起看看下面兩張圖:


圖案鎖

圖案鎖對(duì)應(yīng)布局

通過前面鎖屏加載流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根據(jù)不同的securityMode inflate出來,并添加到界面上的流妻。
我們知道,Pattern鎖所使用的layout是 R.layout.keyguard_pattern_view;

<com.android.keyguard.KeyguardPatternView ...>

...
            <com.android.internal.widget.LockPatternView
                android:id="@+id/lockPatternView"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:layout_marginEnd="8dip"
                android:layout_marginBottom="4dip"
                android:layout_marginStart="8dip"
                android:layout_gravity="center_horizontal"
                android:gravity="center"
                android:clipChildren="false"
                android:clipToPadding="false" />

...
    </FrameLayout>

</com.android.keyguard.KeyguardPatternView>

那么圖案解鎖的滑動(dòng)事件處理椭豫,就是在LockPatternView,是一個(gè)系統(tǒng)公共控件倡鲸,下面我們就分析一下這個(gè)view是如何處理觸摸輸入的:

// frameworks/base/core/java/com/android/internal/widget/LockPatternView.java
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mInputEnabled || !isEnabled()) {
            return false;
        }

        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handleActionDown(event);
                return true;
            case MotionEvent.ACTION_UP:
                handleActionUp();
                return true;
            case MotionEvent.ACTION_MOVE:
                handleActionMove(event);
                return true;
            case MotionEvent.ACTION_CANCEL:
                if (mPatternInProgress) {
                    setPatternInProgress(false);
                    resetPattern();
                    notifyPatternCleared();
                }
                if (PROFILE_DRAWING) {
                    if (mDrawingProfilingStarted) {
                        Debug.stopMethodTracing();
                        mDrawingProfilingStarted = false;
                    }
                }
                return true;
        }
        return false;
    }

幾種事件類型:
事件類型.png

不同的MotionEvent對(duì)應(yīng)幾個(gè)不同的handle方法處理特漩,代碼行數(shù)太多,我們這里大致總結(jié)如下:

  • ACTION_DOWN(handleActionDown):根據(jù)觸摸事件的坐標(biāo)嘹黔,使用算法detectAndAddHit(x, y)獲取是否有命中的點(diǎn)账嚎,如果有,會(huì)調(diào)用addCellToPattern將命中的Cell添加到mPattern中儡蔓,后即回調(diào)mOnPatternListener.onPatternStart()通知監(jiān)聽器郭蕉,KeyguardPatternView實(shí)現(xiàn)并監(jiān)聽了OnPatternListener,做了清除安全提示內(nèi)容的動(dòng)作喂江。另外計(jì)算需要重繪區(qū)域召锈,并調(diào)用invalidate進(jìn)行局部重繪。
  • ACTION_MOVE(handleActionMove):在這里 LockPatternView會(huì)對(duì)所有的歷史坐標(biāo)加當(dāng)前事件坐標(biāo)遍歷for (int i = 0; i < historySize + 1; i++)获询,獲取命中點(diǎn)涨岁,另外如果ACTION_DOWN時(shí)沒有獲取到命中點(diǎn)拐袜,流程同上面的ACTION_UP,然后也會(huì)回調(diào)mOnPatternListener.onPatternStart()。最后會(huì)把所有motionevent對(duì)應(yīng)的重繪區(qū)域進(jìn)行union梢薪,并調(diào)用invalidate進(jìn)行局部重繪蹬铺。

關(guān)于歷史坐標(biāo)
??為了效率,Android系統(tǒng)在處理ACTION_MOVE事件時(shí)會(huì)將連續(xù)的幾個(gè)多觸點(diǎn)移動(dòng)事件打包到一個(gè)MotionEvent對(duì)象中秉撇。我們可以通過getX(int)和getY(int)來獲得最近發(fā)生的一個(gè)觸摸點(diǎn)事件的坐標(biāo)甜攀,然后使用getHistorical(int,int)和getHistorical(int,int)來獲得時(shí)間稍早的觸點(diǎn)事件的坐標(biāo),二者是發(fā)生時(shí)間先后的關(guān)系琐馆。所以赴邻,我們應(yīng)該先處理通過getHistoricalXX相關(guān)函數(shù)獲得的事件信息,然后在處理當(dāng)前的事件信息啡捶。

for (int i = 0; i < historySize + 1; i++) {
    final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
    final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
 ...
}
  • ACTION_UP(handleActionUp):如果mPattern不為空的話姥敛,會(huì)重置mPatternInProgress,取消動(dòng)畫瞎暑,然后回調(diào)mOnPatternListener.onPatternDetected(final List<LockPatternView.Cell> pattern)彤敛,這時(shí)候就開始圖案解鎖的驗(yàn)證了。

  • ACTION_CANCEL:重置pattern狀態(tài)了赌,回調(diào)mOnPatternListener.onPatternCleared()

圖案解鎖驗(yàn)證

// src/com/android/keyguard/KeyguardPatternView.java
    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
    ...
        @Override
        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
           if (DEBUG) Log.d(TAG, "onPatternDetected");
            mKeyguardUpdateMonitor.setCredentialAttempted();
            mLockPatternView.disableInput();
            if (mPendingLockCheck != null) {
                mPendingLockCheck.cancel(false);
            }

            final int userId = KeyguardUpdateMonitor.getCurrentUser();
            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                mLockPatternView.enableInput();
                onPatternChecked(userId, false, 0, false /* not valid - too short */);
                return;
            }

            if (LatencyTracker.isEnabled(mContext)) {
                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
                LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
            }
            mPendingLockCheck = LockPatternChecker.checkCredential(
                    mLockPatternUtils,
                    LockscreenCredential.createPattern(pattern),    // 這里跟進(jìn)去墨榄,會(huì)發(fā)現(xiàn)將 pattern轉(zhuǎn)化成了 byte[]
                    userId,
                    new LockPatternChecker.OnCheckCallback() {

                        @Override
                        public void onEarlyMatched() {
                            if (DEBUG) Log.d(TAG, "onEarlyMatched");
                            if (LatencyTracker.isEnabled(mContext)) {
                                LatencyTracker.getInstance(mContext).onActionEnd(
                                        ACTION_CHECK_CREDENTIAL);
                            }
                            onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
                                    true /* isValidPattern */);
                        }

                        @Override
                        public void onChecked(boolean matched, int timeoutMs) {
                            if (DEBUG) Log.d(TAG, "onChecked matched:" + matched);
                            if (LatencyTracker.isEnabled(mContext)) {
                                LatencyTracker.getInstance(mContext).onActionEnd(
                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
                            }
                            mLockPatternView.enableInput();
                            mPendingLockCheck = null;
                            if (!matched) {
                                onPatternChecked(userId, false /* matched */, timeoutMs,
                                        true /* isValidPattern */);
                            }
                        }

                        @Override
                        public void onCancelled() {
                           if (DEBUG) Log.d(TAG, "onCancelled");
                            // We already got dismissed with the early matched callback, so we
                            // cancelled the check. However, we still need to note down the latency.
                            if (LatencyTracker.isEnabled(mContext)) {
                                LatencyTracker.getInstance(mContext).onActionEnd(
                                        ACTION_CHECK_CREDENTIAL_UNLOCKED);
                            }
                        }
                    });
            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                mCallback.userActivity();
                mCallback.onUserInput();
            }
        }
    ...
    }
 

在繪制密碼后手指抬起的時(shí)候,如果已存的有效點(diǎn)數(shù)達(dá)到4個(gè)及以上勿她,就會(huì)使用LockPatternChecker.checkCredential 方法調(diào)用 task.execute() 啟動(dòng)一個(gè)AsyncTask袄秩, 并在doInBackground中調(diào)用LockPatternUtils.checkCredential 進(jìn)行密碼驗(yàn)證,此時(shí)pattern會(huì)被轉(zhuǎn)化成字節(jié)形式(LockscreenCredential.createPattern(pattern) 這里跟進(jìn)去逢并,會(huì)發(fā)現(xiàn)將 pattern轉(zhuǎn)化成了 byte[])

// LockPatternUtils.java
public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
        if (pattern == null) {
            return new byte[0];
        }
        final int patternSize = pattern.size();

        byte[] res = new byte[patternSize];
        for (int i = 0; i < patternSize; i++) {
            LockPatternView.Cell cell = pattern.get(i);
            res[i] = (byte) (cell.getRow() * 3 + cell.getColumn() + '1');
        }
        return res;
    }

最終和密碼鎖PIN碼鎖一樣之剧,都是遠(yuǎn)程調(diào)用到LockSettingsService 的 checkCredential 接口進(jìn)行驗(yàn)證。

Keyguard接收用戶輸入的密碼會(huì)通過Binder到framework層的LockSettingsService砍聊,LockSettingsService經(jīng)過一系列調(diào)用會(huì)通過getGateKeeperService獲取GateKeeperService然后調(diào)用verifyChallenge方法將密碼繼續(xù)忘底層傳遞背稼,framework的調(diào)用棧如下:


framework的調(diào)用棧.png

驗(yàn)證流程:

// LockSettingsService.java
    private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
            @ChallengeType int challengeType, long challenge, int userId,
            ICheckCredentialProgressCallback progressCallback,
            @Nullable ArrayList<PendingResetLockout> resetLockouts) {

        // 省略部分代碼......

        if (credential == null || credential.isNone()) {
            throw new IllegalArgumentException("Credential can't be null or empty");
        }
        if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
                Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
            Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
            return VerifyCredentialResponse.ERROR;
        }
        
        // response是驗(yàn)證響應(yīng),spBasedDoVerifyCredential發(fā)起驗(yàn)證玻蝌,返回 response 響應(yīng)
        VerifyCredentialResponse response = null;
        response = spBasedDoVerifyCredential(credential, challengeType, challenge,
                userId, progressCallback, resetLockouts);

        // The user employs synthetic password based credential.
        if (response != null) {
            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                sendCredentialsOnUnlockIfRequired(credential, userId);
            }
            return response;
        }
        // 省略部分代碼......
        return response;
    }

這里先看verifyChallenge方法返回有三個(gè)狀態(tài)蟹肘,也就是response.getResponseCode():
??//密碼匹配失敗
??public static final int RESPONSE_ERROR = -1;
??//密碼匹配成功
??public static final int RESPONSE_OK = 0;
??//重試
??public static final int RESPONSE_RETRY = 1;

接下來我們看spBasedDoVerifyCredential()方法做基于 sp 的做驗(yàn)證憑證:

// LockSettingsService.java
    private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
            @ChallengeType int challengeType, long challenge,
            int userId, ICheckCredentialProgressCallback progressCallback,
            @Nullable ArrayList<PendingResetLockout> resetLockouts) {

        // 判斷是否具有生物識(shí)別
        final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);

        // 省略部分代碼......

        final AuthenticationResult authResult;
        VerifyCredentialResponse response;
        synchronized (mSpManager) {
            if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
                return null;
            }
            if (userId == USER_FRP) {
                return mSpManager.verifyFrpCredential(getGateKeeperService(),
                        userCredential, progressCallback);
            }

            long handle = getSyntheticPasswordHandleLocked(userId);
            // 解開基于密碼的合成密碼
            authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
                    getGateKeeperService(), handle, userCredential, userId, progressCallback);

            response = authResult.gkResponse;

            // 省略部分代碼......
        }

        // 省略部分代碼......
        return response;
    }

在這方法里首先進(jìn)行了判斷是不是生物解鎖,然后在調(diào)用合成密碼管理器SyntheticPasswordManager里的unwrapPasswordBasedSyntheticPassword()方法進(jìn)行解密俯树,并將鎖屏密碼往下傳帘腹。

// SyntheticPasswordManager.java
    public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
            long handle, @NonNull LockscreenCredential credential, int userId,
            ICheckCredentialProgressCallback progressCallback) {

        // 省略部分代碼......

          else {
            byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
            GateKeeperResponse response;
            try {
                Log.d("yexiao","yexiao:",new Throwable());
                response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
                        pwd.passwordHandle, gkPwdToken);
            } catch (RemoteException e) {
                Slog.e(TAG, "gatekeeper verify failed", e);
                result.gkResponse = VerifyCredentialResponse.ERROR;
                return result;
            }

            // 省略部分代碼......
        }

        // 省略部分代碼......

        return result;
    }

unwrapPasswordBasedSyntheticPassword中的gatekeeper是LockSettingsService的getGateKeeperService方法獲取的IGateKeeperService Binder代理端:

// LockSettingsService.java
    protected synchronized IGateKeeperService getGateKeeperService() {
        if (mGateKeeperService != null) {
            return mGateKeeperService;
        }

        final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
        if (service != null) {
            try {
                service.linkToDeath(new GateKeeperDiedRecipient(), 0);
            } catch (RemoteException e) {
                Slog.w(TAG, " Unable to register death recipient", e);
            }
            mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
            return mGateKeeperService;
        }

        Slog.e(TAG, "Unable to acquire GateKeeperService");
        return null;
    }

這里有個(gè)問題,我們發(fā)現(xiàn)IGateKeeperService的Binder實(shí)現(xiàn)端找不到许饿,而且在Framework層也找不到在那里注冊(cè)的service阳欲,為何能getService?

其實(shí)IGateKeeperService這個(gè)AIDL文件的具體實(shí)現(xiàn)類不像傳統(tǒng)的Framework Binder服務(wù),它的實(shí)現(xiàn)端在native層胸完,我們前面說了GateKeeper的架構(gòu)书释,提到GateKeeper是一種C++的Binder服務(wù),與java層接口相對(duì)應(yīng)

我們就先來來看看GateKeeper server端赊窥,目錄system/core/gatekeeperd下的gatekeeperd.cpp類

// gatekeeperd.cpp
int main(int argc, char* argv[]) {
    ALOGI("Starting gatekeeperd...");
    if (argc < 2) {
        ALOGE("A directory must be specified!");
        return 1;
    }
    if (chdir(argv[1]) == -1) {
        ALOGE("chdir: %s: %s", argv[1], strerror(errno));
        return 1;
    }

    android::sp<android::IServiceManager> sm = android::defaultServiceManager();
    android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
    android::status_t ret = sm->addService(
            android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
    if (ret != android::OK) {
        ALOGE("Couldn't register binder service!");
        return -1;
    }

    /*
     * We're the only thread in existence, so we're just going to process
     * Binder transaction as a single-threaded program.
     */
    android::IPCThreadState::self()->joinThreadPool();
    return 0;
}

在main函數(shù)中爆惧,首先獲取BpSeviceManager,然后創(chuàng)建GateKeeperProxy類锨能,在調(diào)用addService函數(shù)將GateKeeperProxy注冊(cè)到SeviceManager扯再,名稱為"android.service.gatekeeper.IGateKeeperService",前面我們?cè)贔ramework層通過getService(Context.GATEKEEPER_SERVICE)獲取的gatekeeper服務(wù)其實(shí)獲取的就是這個(gè)服務(wù)址遇,Context中定義的服務(wù)名稱也是一樣的熄阻。

至于native層與Java層如何相互調(diào)用關(guān)聯(lián)的,這里就省略了。

前面解密過程調(diào)的verifyChallenge方法調(diào)到了gatekeeperd.cpp中的GateKeeperProxy類的同名verifyChallenge函數(shù),但我們又發(fā)現(xiàn)這兩個(gè)verifyChallenge方法參數(shù)并不一致免绿,這無所謂的,Binder調(diào)用并不需要client端和server端參數(shù)一致钾军,調(diào)用方法的匹配是通過Binder code來決定的。

到這里绢要,上層的鎖屏密碼就已經(jīng)傳遞到了natice層吏恭,還記得前面說的gatekeeper架構(gòu)嗎,native層過了之后就該通過HIDL忘HAL層發(fā)送密碼了重罪,來看看GateKeeperProxy中的verifyChallenge具體實(shí)現(xiàn):

// gatekeeperd.cpp
    Status verifyChallenge(int32_t uid, int64_t challenge,
                           const std::vector<uint8_t>& enrolledPasswordHandle,
                           const std::vector<uint8_t>& providedPassword,
                           GKResponse* gkResponse) override {

        //省略掉一些權(quán)限相關(guān)檢查 ......
        //省略一些數(shù)據(jù)類型轉(zhuǎn)換.....
        Return<void> hwRes = hw_device->verify(
                hw_uid, challenge, curPwdHandle, enteredPwd,
                [&gkResponse](const GatekeeperResponse& rsp) {
                    if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
                        ALOGD("gatekeeperd verify process ok");
                        *gkResponse = GKResponse::ok(
                                {rsp.data.begin(), rsp.data.end()},
                                rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
                    } else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
                        ALOGD("gatekeeperd verify process retry timeout");
                        *gkResponse = GKResponse::retry(rsp.timeout);
                    } else {
                        ALOGD("gatekeeperd verify fail,maybe not match");
                        *gkResponse = GKResponse::error();
                    }
                });

        if (!hwRes.isOk()) {
            LOG(ERROR) << "verify transaction failed";
            return GK_ERROR;
        }
        // 省略部分代碼......
        return Status::ok();
    }

到這里基本整個(gè)解鎖流程結(jié)束了樱哼,native層也不在繼續(xù)深入分析了。

在整個(gè)操作過程中剿配,mOnPatternListener 被用于通知LockPatternView進(jìn)行安全鎖提示內(nèi)容和Pattern狀態(tài)的刷新搅幅。

我們可以對(duì)整個(gè)密碼匹配的流程進(jìn)行總結(jié)了:

  • 上層Keyguard接收用戶的密碼輸入。
  • 收到密碼后通過Binder將密碼傳遞到LockSettingsService惨篱。
  • 在LockSettingsService中獲取到實(shí)現(xiàn)在native層的GateKeeperService盏筐,調(diào)用其verifyChallenge函數(shù)围俘。
  • verifyChallenge中調(diào)用HIDL服務(wù)IGatekeeper的verify函數(shù)繼續(xù)向HAL中發(fā)送密碼砸讳。
  • IGatekeeper中獲取名為GATEKEEPER_HARDWARE_MODULE_ID的HAL模塊,并打開其下的device界牡,調(diào)用device的verify函數(shù)在TEE硬件中進(jìn)最終密碼匹配簿寂。

參考:Android P keyguard 初始化,Pattern解鎖等介紹 宿亡、SystemUI 之圖案鎖驗(yàn)證流程常遂、AndroidQ 鎖屏密碼驗(yàn)證解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挽荠,隨后出現(xiàn)的幾起案子克胳,更是在濱河造成了極大的恐慌平绩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漠另,死亡現(xiàn)場(chǎng)離奇詭異捏雌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笆搓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門性湿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人满败,你說我怎么就攤上這事肤频。” “怎么了算墨?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵宵荒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我净嘀,道長(zhǎng)骇扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任面粮,我火速辦了婚禮少孝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘熬苍。我一直安慰自己稍走,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布柴底。 她就那樣靜靜地躺著婿脸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柄驻。 梳的紋絲不亂的頭發(fā)上狐树,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音鸿脓,去河邊找鬼抑钟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛野哭,可吹牛的內(nèi)容都是我干的在塔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼拨黔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蛔溃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤贺待,失蹤者是張志新(化名)和其女友劉穎徽曲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麸塞,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疟位,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喘垂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甜刻。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖正勒,靈堂內(nèi)的尸體忽然破棺而出得院,到底是詐尸還是另有隱情,我是刑警寧澤章贞,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布祥绞,位于F島的核電站,受9級(jí)特大地震影響鸭限,放射性物質(zhì)發(fā)生泄漏蜕径。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一败京、第九天 我趴在偏房一處隱蔽的房頂上張望兜喻。 院中可真熱鬧,春花似錦赡麦、人聲如沸朴皆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遂铡。三九已至,卻和暖如春晶姊,著一層夾襖步出監(jiān)牢的瞬間扒接,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工们衙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钾怔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓砍艾,卻偏偏與公主長(zhǎng)得像蒂教,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脆荷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容