作者归档:softsim

Apple Watch IMEI信息

2022年中国制造 Watch SE 45mm 型号 A2856

IMEI:  35469797 0465568
       35469797 0179617
EID:   89049032007108883000131977530047
2022年产 Watch SE 2代  型号 A2856
35469797 0465568
89049032007108883000131977530047

2020年 apple s4 44mm apple认证的翻新产品

35868009 0053312
89049032004008883000021439339504

2018年 S3

IMEI: 359447080303639
EID:  89049032003008883....

2021年生产 Watch SE 一代,型号A2356

35295111 1930317
89049032006008883000150793755265

2022年产 Watch SE 一代 型号 A2355

35294211 3249152
89049032006008883000150902912566

2022年生产 Watch S7 45mm 型号 A2478

350813505808446
89049032007008883000092511582246

2021年 中国制造 S7 45mm 型号 A2478

35822763 1920813
89049032007008883000089533994683

2022年 中国制造 A2858 S8 45mm

355387177587922
89049032007108883000131950747183

2019年 S4 44mm

358682090250328
89049032004008883000021408341214

2022年中国制造 A2858 S8 45mm

355387172600175
89049032007108883000134610222276

2021年中国制造 S7 45mm A2478

350031570934266
89049032007008883000086710808422

2022年 中国制造 S8 41mm A2857

350950934806334
89049032007108883000134604480264

2022年 中国制造 A2859 Ultra 49mm

356881867653613
89049032007108883000134614178518

2020年 S6 44mm

89049032003008882400000391479138
35296811 9284075
35292581 1928407

在Android中用Kotlin协程将callback转换为同步调用

lifecycleScope
viewModelScope

applicationScope

coroutineScope

GlobalScope

的区别

使用一个和应用生命周期一样长的自定义的CoroutineScope解决这个问题,自定义CoroutineScope的优势在于可以使用自定义的CoroutineExceptionHandler和自定义的thread pool(可以定制一个完全符合自己需求的CoroutineContext)。

我们使用applicationScope来命名这个自定义的CoroutineScope,并且它必须包含一个SupervisorJob(),这样任何子coroutine的执行失败不会影响到该scope下其他的子coroutines,也不会导致applicationScope本身的退出。

class MyApplication : Application() {
  // No need to cancel this scope as it'll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}


我们不需要处理applicationScope的退出,因为它的生命周期和我们的应用一样长,所以我们也不需要持有SupervisorJob()的句柄。我们可以使用applicationScope来启动那些需要和我们App生命周期一样长的coroutines。
对于那些我们不希望被取消执行的coroutines,我们都可以使用applicationScope。
当你创建一个新的Repository实例的时候,就可以传入applicationScope。

suspend
withContext
Dispatchers.IO

调度程序:调度程序帮助协程决定必须在哪个线程上完成工作。
调度程序主要分为三种类型:IO、Default 和 Main。
IO调度程序用于完成网络和磁盘相关的工作。
Default用于执行 CPU 密集型工作。
Main是Android的UI线程。

还有一个在Android上可能不常用
Dispatchers.Unconfined
不限于任何特定线程的协程调度程序。它在当前调用帧中执行协程的初始延续,并让协程在相应挂起函数使用的任何线程中恢复,而无需强制执行任何特定的线程策略。
它不会改变线程。当它启动时,它在启动它的线程上运行。如果恢复,它将在恢复它的线程上运行。

假设有一个使用回调的库

Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})

用协程将它转换为同步函数

suspend fun doSomething(): Result {

    return suspendCoroutine { continuation ->
        Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
    }
}

步骤:
1. 创建一个suspend函数来返回 onSucess的 Result
2. 用 suspendCoroutine返回块 来返回
3. 在onSucess回调中,用 continuation.resume(result) 来恢复
4. 用 continuation.resumeWithException(throwable) 抛出错误

在launch块里使用 创建的suspend函数

launch {
    val result = doSomething()
}

有时候需要用到 suspendCancellableCoroutine, 而不是 suspendCoroutine
假设该库还支持取消任务

val id = Library.doSomething(object : Listener {
    override fun onSuccess(result: Result) {
    }
    override fun onError(throwable: Throwable) {
    }
})
可以用id来取消任务
Library.cancel(id)

转换成

suspend fun doSomething(): Result {
    return suspendCancellableCoroutine { continuation ->
        val id = Library.doSomething(object : Listener {
            override fun onSuccess(result: Result) {
                continuation.resume(result)
            }
            override fun onError(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
        continuation.invokeOnCancellation {
            Library.cancel(id)
        }
    }
}

withContext是一个suspend函数

withContext(Dispatchers.Default) {
    delay(2000)
    return@withContext 10
}

withContext不会创建新的协程,它只会改变现有协程的上下文,它是一个suspend函数,
而launch和async创建一个新的协程,它们不是suspend函数。

用async 启动协程,然后用await得到返回结果。

launch{}返回 一个Job并且 不携带任何结果值,
async{}返回一个实例Deferred t,该实例 有一个await()函数, 用来返回协程结果。

launch和async之间的另一个区别在于异常处理。
如果launch块内出现任何异常,如果我们没有处理它,应用程序就会崩溃。
但是,如果async块内出现任何异常,它会存储在结果中Deferred并且不会传递到其他任何地方,除非我们处理它,否则它将被悄悄丢弃。

将Camera API由回调改协程

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
    suspendCoroutine { cont ->
        val callback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cont.resume(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
                cont.resume(null)
            }

            override fun onError(camera: CameraDevice, error: Int) {
                // assuming that we don't care about the error in this example
                cont.resume(null) 
            }
        }
        openCamera(cameraId, callback, null)
    }

生产者/消费者

val channel = Channel<ByteArray>
send()
receive()

挂起函数应该能够安全地从主线程调用

挂起函数应该是主线程安全的,这意味着,您可以安全地从主线程调用挂起函数。如果某个类在协程中执行长期运行的阻塞操作,那么该类负责使用 withContext 将执行操作移出主线程。这适用于应用中的所有类,无论其属于架构的哪个部分都不例外。

withContext(Dispatchers.IO) {
    ...
}

返回协程中产生的值

val result = runBlocking(CommonPool) {
         suspendingFunctionThatReturnsMyValue()
}

将会一直阻塞,直到结果可用

runBlocking 将运行一个新的协程,并阻塞当前线程,知道协程运行完成。
这个函数,不应该从 协程中调用。
它设计成 桥接 普通的 代码块 到 suspend风格的代码库(也就是协程写的代码库)。
一般用在 main函数, 或者 测试代码中

最好是同 withContext(Dispachedr.IO) 一起用。

因为阻塞主线程不是一个好注意。并且当有多个协程 同 runBlocking唤起 的协程,运行在同一个线程时,会将其他协程也阻塞住。

Kotlin has special ways to deal with it, such as the IO Dispatcher, which will run the code on its own, isolated thread so that it doesn’t disrupt your other coroutines.

北京移动服务密码修改

1. 您可以登录北京移动门户网站办理。当您的客服密码遗忘或丢失时,全球通、动感地带、神州行客户可以办理密码重置业务,相关手续如下:
(1)通话记录验证:验证临时校验码、提供近90天内3天以前的5条通话或短信记录,其中至少有3条是主叫通话记录。验证通过后,系统会提示您自行填写6位或8位密码,填写完成后,系统会为您下发密码重置成功的短信,短信内容包含已重置的新密码。
(2)身份验证:个人客户可验证入网时登记的机主姓名和证件号码办理;单位客户可验证入网时登记的单位名称和单位证件号码,同时还需填写使用人或责任人证件号码办理。验证通过后,系统将通过短信为您发送一个新的6位密码,你可自行修改。
—————————-
2. 短信
(1)密码修改
全球通、动感地带、神州行畅听卡、5元卡客户:发送“MMXG空格原密码空格新密码”至10086修改客服密码。
神州行升级版标准卡、家园卡客户:发送“MMBG空格原密码空格新密码”至10086修改客服密码。
(2)密码重置
北京移动个人客户/单位客户,均可发送“MMCZ空格证件号码空格新密码空格新密码”至10086重新设置客服密码。单位客户办理时,短信指令中的证件号码是客户入网时使用的注册登记证照上的号码,您发送短信后还需按短信提示回复使用人证件号码进行验证。
——————
3. 10086自动台
您可以本机拨打10086按提示修改客服密码。已办理实名制认证的全球通、动感地带、神州行升级版标准卡、畅听卡、家园卡、5元卡客户,还可以本机拨打10086自动台,按提示成功验证身份证后,10086会将系统自动生成的新密码发送到您的本机号码上。
———————

办理限制:
1、每月最后一天19点后无法办理。(不含10086自动台办理密码修改)
2、您通过网站、短信、10086自动台申请密码重置时,同一手机号码在30天内最多可以成功办理2次,通过营业厅办理密码重置没有次数限制,但30天内您通过各渠道办理密码重置的次数将累加计算。
3、您在30天内(从操作当天起向前推算30天)最多只能通过网站渠道办理5次密码重置,达到5次后,将无法再通过网站办理。若您已有2次成功办理的记录,则30天内无法再通过网站办理,此时将不再执行最多5次的规定。
4、目前仅限北京移动全球通以及已实名制的动感地带、神州行升级版标准卡、畅听卡、家园卡、5元卡、无线座机卡客户,在营业厅办理密码修改。

selenium模拟手机浏览器访问网页

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
  

chrome_options = Options()
  

chrome_options.add_argument('--headless=new')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')

mobile_emulation = {
    "deviceMetrics": { "width": 720, "height": 1280, "pixelRatio": 1 },
    "userAgent": "Mozilla/5.0 (Linux; Android 12; Pixel 5 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36" }

chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)    
      

browser = webdriver.Chrome(options=chrome_options)
  

browser.get('https://www.baidu.com/')
  

print(browser.title)
browser.get_screenshot_as_file("capture.png")


browser.close()

还可跟页面交互

CCID接口规范

控制通道(Control pipe)
GET_DESCRIPTOR 和 SET_CONFIGURATION 等标准请求 通过此通道发送。

命令在默认通道上发动,报告信息也在默认通道上返还给主机。
———————–
中断通道处理异步事件
NotifySlotChange (卡插入,移除)
HardwareError (硬件错误)
————————
Bulk-in, Bulk-out通道

CCID命令在BULK_OUT端点 发送
每个命令都会有一个响应。有些响应可以立即得到。
有些响应 需要通过 BULK-IN 端口返回。

发送给指定卡槽的所有命令,都必须是同步发送的。
一个卡槽同一时刻,只能接受一个命令。
如果一个卡槽准备接受一个命令,那么就应该认为它是空闲的。

如果每条命令 都是给不同的空闲卡槽的,那么主机最多可以同时发送 bMaxCCIDBusySlots 条命令给 CCID
主机需要记住哪些slot是繁忙的。
当激活的卡槽数量达到 bMaxCCIDBusySlots时, CCID只能在 控制通道,接受 通用请求 或者 特定类的请求,去 ABORT一个slot

当CCID成功接收到一条给 繁忙卡槽的命令时,它应该报一个 CMD_SLOT_BUSY 错误

为了实现对每条命令的跟踪,主机对 发出的命令,设置一个 唯一的 bSeq 命令标识符。
CCID发回响应时,也要带上同样的 bSeq 标识符,表明 这条命令已经处理完毕。
驱动程序不会发送新的命令给 slot, 直到前面发送命令的 ending response响应被收到。
如果命令有错误或者超时, 那么 会发送 abandon/abort 类特定的命令 给 控制端点。

对于一个 BULK-OUT 命令 消息,可能会产生多个 BULK-IN 响应消息。
For example, the CCID can send a BULK-IN message with a Time Extension status to notify
the host that the ICC has requested more time to process the ICC command and, after a
delay, follow this with a second BULK-IN message with the ICC’s response to the
command. When this happens, both BULK-IN messages have the same bSeq value.

如果Bulk-In 消息的大小 是 MaxPacketSize 的倍数,那么应该在它后面还发一个 Zero Length Packet (ZLP)
ZLP包可以让 CCID设备驱动更有效率, 是一种最佳实践。

———————————————
CCID 发送 RDR_to_PC_NotifySlotChange 到主机, 通知驱动程序, 新插入了卡
收到驱动来的 PC_to_RDR_IccPowerOn 消息, CCID重新激活 IC卡

PC_to_RDR_IccPowerOn         RDR_to_PC_DataBlock
PC_to_RDR_IccPowerOff        RDR_to_PC_SlotStatus
PC_to_RDR_GetSlotStatus      RDR_to_PC_SlotStatus
PC_to_RDR_XfrBlock           RDR_to_PC_DataBlock
PC_to_RDR_GetParameters      RDR_to_PC_Parameters
PC_to_RDR_ResetParameters    RDR_to_PC_Parameters
PC_to_RDR_SetParameters      RDR_to_PC_Parameters
PC_to_RDR_Escape             RDR_to_PC_Escape
PC_to_RDR_IccClock           RDR_to_PC_SlotStatus
PC_to_RDR_T0APDU             RDR_to_PC_SlotStatus
PC_to_RDR_Secure             RDR_to_PC_DataBlock
PC_to_RDR_Mechanical         RDR_to_PC_SlotStatus
PC_to_RDR_Abort              RDR_to_PC_SlotStatus
PC_to_RDR_SetDataRateAndClockFrequency              RDR_to_PC_DataRateAndClockFrequency

CCID_PC_To_RDR Commands

CodeCommandDescriptionEquivalent PC/SC function
62PC_To_RDR_IccPowerOnPower up the card in the slot, and return its ATRSCardConnect
63PC_To_RDR_IccPowerOffPower down the card in the slotSCardDisconnect
65PC_To_RDR_GetSlotStatusRetrieve the slot’s current status and protocol dataSCardStatus
6BPC_To_RDR_EscapeSend a direct command to the deviceSCardControl
6FPC_To_RDR_XfrBlockSend a C-APDU to the card (and return its R-APDU)SCardTransmit

CCID_RDR_To_PC Responses

CodeCommandDescriptionIn response to
80RDR_To_PC_DataBlockR-APDU returned by the card
ATR of the card
PC_To_RDR_XfrBlock (success)
PC_To_RDR_IccPowerOn (success)
81RDR_To_PC_SlotStatusStatus of the slotPC_To_RDR_GetSlotStatus
PC_To_RDR_IccPowerOff
PC_To_RDR_XfrBlock (failure)
PC_To_RDR_IccPowerOn (failure)
83RDR_To_PC_EscapeSend a direct command to the devicePC_To_RDR_Escape

Smart card ATR parsing

在线分析

Smart card ATR parsing

源代码: https://github.com/LudovicRousseau/pyscard-contrib/tree/master/parseATR

https://android.googlesource.com/platform/frameworks/opt/telephony/+/master/src/java/com/android/internal/telephony/uicc/AnswerToReset.java

从ATR中就可以看出是否支持eUICC功能

TB(3) = 0x82 –> eUICC-related functions supported

3B 9F 96 80 3F C7 82 80 31 E0 73 FE 21 1B 63 3A 20 4E 83 00 90 00 31

3B 9F 97 80 3F C7 82 80 31 E0 73 FE 21 1F 64 08 63 62 00 82 90 00 6F

3B 9F 97 C0 0A 3F C7 82 80 31 E0 73 FE 21 1F 65 D0 02 1A 14 A2 81 0F CF

3B 9F 96 80 3F C7 82 80 31 E0 73 F6 21 57 57 4A 4D 02 0B 60 50 00 38

纯Python的Web UI

只需要懂Python就可以了,不再需要学习 JavaScript HTML CSS
NiceGUI

NiceGUI httpx coroutine button click

import asyncio
import httpx
from nicegui import ui


async def add(url):
    print('----')
    l = ui.label('This should be visible')


    async with httpx.AsyncClient() as client:
        r = await client.get(url)
        res = r.json()
        l.text = res['ip']


ip_url = 'https://api.seeip.org/jsonip'
#ip_url = 'https://api.ipify.org/?format=json'

ui.button('Add label', on_click= lambda:add(ip_url))

ui.run(reload=False,  host="::", port=5678, favicon='🚀', title='httpx协程') 

报错

 RuntimeWarning: coroutine 'add' was never awaited
  self.on('click', lambda _: handle_event(on_click, ClickEventArguments(sender=self, client=self.client)))
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

修改成不带参数的

import asyncio
import httpx
from nicegui import ui



#ip_url = 'https://api.seeip.org/jsonip'
ip_url = 'https://api.bigdatacloud.net/data/client-ip'

async def add():
    print('----')
    l = ui.label('Waiting...')


    async with httpx.AsyncClient() as client:
        r = await client.get(ip_url)
        res = r.json()
        l.text = res['ipString']



ui.button('Get IP', on_click=add)

ui.run(reload=False,  host="::", port=5678, favicon='🚀', title='httpx协程') 

真有传递参数,还是有办法的

How to pass an argument to an async function with a ui.button

import asyncio
import httpx
from nicegui import ui
from functools import partial


ip_url = 'https://api.seeip.org/jsonip'
#ip_url = 'https://ipv6.jsonip.com/'
#  https://ipv4.jsonip.com/
#  http://no-tls.jsonip.com/

async def add(url):
    print('----')
    l = ui.label('Waiting...')

    async with httpx.AsyncClient() as client:
        r = await client.get(url)
        res = r.json()
        l.text = res['ip']

ui.button('Get IP', on_click=partial(add, ip_url))

ui.run(reload=False,  host="::", port=5678, favicon='🚀', title='httpx协程') 

就是用 functools 的 partial