クマは森で用を足しますか?

アウトプットは重要です。

Google Pixel 4 (QD1A.190921.007) System Property 一覧

セットアップを終えた直後のものです。また確認したくなることがあるかもしれないので、ここに貼っておきます。

[DEVICE_PROVISIONED]: [1]
[aaudio.hw_burst_min_usec]: [2000]
[aaudio.mmap_exclusive_policy]: [2]
[aaudio.mmap_policy]: [2]
[af.fast_track_multiplier]: [1]
[dalvik.vm.appimageformat]: [lz4]
[dalvik.vm.dex2oat-Xms]: [64m]
[dalvik.vm.dex2oat-Xmx]: [512m]
[dalvik.vm.dex2oat-max-image-block-size]: [524288]
[dalvik.vm.dex2oat-minidebuginfo]: [true]
[dalvik.vm.dex2oat-resolve-startup-strings]: [true]
[dalvik.vm.dexopt.secondary]: [true]
[dalvik.vm.heapgrowthlimit]: [192m]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[dalvik.vm.heapsize]: [512m]
[dalvik.vm.heapstartsize]: [8m]
[dalvik.vm.heaptargetutilization]: [0.75]
[dalvik.vm.image-dex2oat-Xms]: [64m]
[dalvik.vm.image-dex2oat-Xmx]: [64m]
[dalvik.vm.isa.arm.features]: [default]
[dalvik.vm.isa.arm.variant]: [cortex-a76]
[dalvik.vm.isa.arm64.features]: [default]
[dalvik.vm.isa.arm64.variant]: [cortex-a76]
[dalvik.vm.minidebuginfo]: [true]
[dalvik.vm.usejit]: [true]
[dalvik.vm.usejitprofiles]: [true]
[debug.atrace.tags.enableflags]: [0]
[debug.force_rtl]: [false]
[debug.gralloc.enable_fb_ubwc]: [1]
[debug.media.codec2]: [2]
[debug.sf.early_app_phase_offset_ns]: [500000]
[debug.sf.early_gl_app_phase_offset_ns]: [15000000]
[debug.sf.early_gl_phase_offset_ns]: [3000000]
[debug.sf.early_phase_offset_ns]: [500000]
[debug.sf.enable_egl_image_tracker]: [1]
[debug.sf.enable_gl_backpressure]: [1]
[debug.sf.high_fps_early_gl_phase_offset_ns]: [9000000]
[debug.sf.high_fps_early_phase_offset_ns]: [6100000]
[debug.sf.hw]: [1]
[debug.sf.phase_offset_threshold_for_next_vsync_ns]: [6100000]
[debug.stagefright.c2inputsurface]: [-1]
[debug.stagefright.ccodec]: [4]
[debug.stagefright.omx_default_rank]: [512]
[dev.bootcomplete]: [1]
[dev.mnt.blk.data]: [dm-7]
[dev.mnt.blk.metadata]: [sda]
[dev.mnt.blk.mnt.vendor.persist]: [sda]
[dev.mnt.blk.product]: [dm-6]
[dev.mnt.blk.root]: [dm-4]
[dev.mnt.blk.vendor]: [dm-5]
[dev.mnt.blk.vendor.firmware_mnt]: [sda]
[drm.service.enabled]: [true]
[gsm.current.phone-type]: [1]
[gsm.network.type]: [Unknown]
[gsm.operator.alpha]: []
[gsm.operator.iso-country]: [jp]
[gsm.operator.isroaming]: [false]
[gsm.operator.numeric]: []
[gsm.sim.state]: [ABSENT]
[gsm.version.baseband]: [g8150-00014-190826-B-5830341]
[gsm.version.ril-impl]: [Qualcomm RIL 1.0]
[hwservicemanager.ready]: [true]
[init.svc.adbd]: [running]
[init.svc.airbrush]: [running]
[init.svc.apexd]: [running]
[init.svc.apexd-bootstrap]: [stopped]
[init.svc.ashmemd]: [running]
[init.svc.audioserver]: [running]
[init.svc.bootanim]: [stopped]
[init.svc.bpfloader]: [stopped]
[init.svc.cameraserver]: [running]
[init.svc.cnd]: [running]
[init.svc.cnss-daemon]: [running]
[init.svc.color_init]: [stopped]
[init.svc.confirmationui-1-0]: [running]
[init.svc.drm]: [running]
[init.svc.gatekeeper-1-0]: [running]
[init.svc.gatekeeperd]: [running]
[init.svc.gnss_service]: [running]
[init.svc.gpu]: [running]
[init.svc.gsid]: [stopped]
[init.svc.hal_neuralnetworks_darwinn]: [running]
[init.svc.hidl_memory]: [running]
[init.svc.hwservicemanager]: [running]
[init.svc.idmap2d]: [stopped]
[init.svc.incidentd]: [running]
[init.svc.init-radio-sh]: [stopped]
[init.svc.init-sensors-sh]: [stopped]
[init.svc.insmod_sh]: [stopped]
[init.svc.installd]: [running]
[init.svc.iorapd]: [stopped]
[init.svc.irsc_util]: [stopped]
[init.svc.keymaster-4-0]: [running]
[init.svc.keystore]: [running]
[init.svc.lmkd]: [running]
[init.svc.loc_launcher]: [running]
[init.svc.logd]: [running]
[init.svc.logd-auditctl]: [stopped]
[init.svc.logd-reinit]: [stopped]
[init.svc.media]: [running]
[init.svc.media.swcodec]: [running]
[init.svc.mediadrm]: [running]
[init.svc.mediaextractor]: [running]
[init.svc.mediametrics]: [running]
[init.svc.modem_svc]: [running]
[init.svc.netd]: [running]
[init.svc.neuralnetworks_hal_service]: [running]
[init.svc.nfc_hal_service]: [running]
[init.svc.pd_mapper]: [running]
[init.svc.per_proxy]: [running]
[init.svc.qteeconnector-hal-1-0]: [running]
[init.svc.rlsservice]: [running]
[init.svc.rmt_storage]: [running]
[init.svc.secure_element_hal_service]: [running]
[init.svc.sensors.qti]: [running]
[init.svc.servicemanager]: [running]
[init.svc.statsd]: [running]
[init.svc.storaged]: [running]
[init.svc.surfaceflinger]: [running]
[init.svc.system_suspend]: [running]
[init.svc.tftp_server]: [running]
[init.svc.time_daemon]: [running]
[init.svc.tombstoned]: [running]
[init.svc.traced]: [running]
[init.svc.traced_probes]: [running]
[init.svc.tui_comm-1-0]: [running]
[init.svc.ueventd]: [running]
[init.svc.update_engine]: [running]
[init.svc.update_verifier_nonencrypted]: [stopped]
[init.svc.usbd]: [stopped]
[init.svc.vendor-qti-media-c2-hal-1-0]: [running]
[init.svc.vndservicemanager]: [running]
[init.svc.vold]: [running]
[init.svc.wait_for_strongbox]: [stopped]
[init.svc.wificond]: [running]
[init.svc.wireless_charger]: [running]
[init.svc.zygote]: [running]
[init.svc.zygote_secondary]: [running]
[keyguard.no_require_sim]: [true]
[log.tag.APM_AudioPolicyManager]: [D]
[log.tag.stats_log]: [I]
[masterclear.allow_retain_esim_profiles_after_fdr]: [true]
[media.aac_51_output_enabled]: [true]
[media.mediadrmservice.enable]: [true]
[media.stagefright.enable-aac]: [true]
[media.stagefright.enable-http]: [true]
[media.stagefright.enable-player]: [true]
[media.stagefright.enable-qcp]: [true]
[media.stagefright.enable-scan]: [true]
[mm.enable.qcom_parser]: [13631487]
[mm.enable.smoothstreaming]: [true]
[mmp.enable.3g2]: [true]
[net.bt.name]: [Android]
[net.qtaguid_enabled]: [1]
[net.tcp.default_init_rwnd]: [60]
[nfc.initialized]: [true]
[partition.product.verified]: [2]
[partition.system.verified]: [2]
[partition.vendor.verified]: [2]
[persist.data.df.agg.dl_pkt]: [10]
[persist.data.df.agg.dl_size]: [4096]
[persist.data.df.dev_name]: [rmnet_usb0]
[persist.data.df.dl_mode]: [5]
[persist.data.df.iwlan_mux]: [9]
[persist.data.df.mux_count]: [8]
[persist.data.df.ul_mode]: [5]
[persist.data.mode]: [concurrent]
[persist.data.netmgrd.qos.enable]: [true]
[persist.data.wda.enable]: [true]
[persist.fuse_sdcard]: [true]
[persist.logd.size]: [16777216]
[persist.mm.enable.prefetch]: [true]
[persist.rcs.supported]: [1]
[persist.rild.nitz_long_ons_0]: []
[persist.rild.nitz_long_ons_1]: []
[persist.rild.nitz_long_ons_2]: []
[persist.rild.nitz_long_ons_3]: []
[persist.rild.nitz_plmn]: []
[persist.rild.nitz_short_ons_0]: []
[persist.rild.nitz_short_ons_1]: []
[persist.rild.nitz_short_ons_2]: []
[persist.rild.nitz_short_ons_3]: []
[persist.rmnet.data.enable]: [true]
[persist.sys.boot.reason]: []
[persist.sys.boot.reason.history]: [reboot,userrequested,1590826984
reboot,userrequested,1590826887]
[persist.sys.dalvik.vm.lib.2]: [libart.so]
[persist.sys.device_provisioned]: [1]
[persist.sys.disable_rescue]: [true]
[persist.sys.displayinset.top]: [0]
[persist.sys.isolated_storage]: [true]
[persist.sys.locale]: [en-US]
[persist.sys.sf.color_mode]: [9]
[persist.sys.sf.color_saturation]: [1.0]
[persist.sys.sf.native_mode]: [2]
[persist.sys.timezone]: [Asia/Tokyo]
[persist.sys.usb.config]: [adb]
[persist.timed.enable]: [true]
[persist.traced.enable]: [1]
[persist.vendor.display.enable_kernel_idle_timer]: [true]
[pm.dexopt.ab-ota]: [speed-profile]
[pm.dexopt.bg-dexopt]: [speed-profile]
[pm.dexopt.boot]: [verify]
[pm.dexopt.first-boot]: [quicken]
[pm.dexopt.inactive]: [verify]
[pm.dexopt.install]: [speed-profile]
[pm.dexopt.shared]: [speed]
[ril.ecclist]: [911,*911,112,000,110,08,#911,999,119,118]
[ro.actionable_compatible_property.enabled]: [true]
[ro.adb.secure]: [1]
[ro.allow.mock.location]: [0]
[ro.apex.updatable]: [true]
[ro.atrace.core.services]: [com.google.android.gms,com.google.android.gms.ui,com.google.android.gms.persistent]
[ro.audio.monitorRotation]: [true]
[ro.baseband]: [sdm]
[ro.board.platform]: [msmnile]
[ro.boot.avb_version]: [1.1]
[ro.boot.baseband]: [sdm]
[ro.boot.blockchain]: [disabled]
[ro.boot.boot_devices]: [soc/1d84000.ufshc]
[ro.boot.bootdevice]: [1d84000.ufshc]
[ro.boot.bootloader]: [c2f2-0.2-5799621]
[ro.boot.bootreason]: [reboot]
[ro.boot.boottime]: [0BLE:58,1BLL:32,1BLE:560,2BLL:154,2BLE:1007,SW:0,KL:0,KD:92,ODT:217,AVB:64]
[ro.boot.cdt_hwid]: [0x05062801]
[ro.boot.cid]: [00000000]
[ro.boot.ddr_info]: [Hynix]
[ro.boot.ddr_size]: [6GB]
[ro.boot.dtb_idx]: [0]
[ro.boot.dtbo_idx]: [11]
[ro.boot.dynamic_partitions]: [true]
[ro.boot.flash.locked]: [1]
[ro.boot.force_normal_boot]: [1]
[ro.boot.hardware]: [flame]
[ro.boot.hardware.color]: [BLK]
[ro.boot.hardware.coo]: [CN]
[ro.boot.hardware.ddr]: [6GB,Hynix,LPDDR4X]
[ro.boot.hardware.dsds]: [0]
[ro.boot.hardware.platform]: [sm8150]
[ro.boot.hardware.radio.subtype]: [1]
[ro.boot.hardware.revision]: [MP1.0]
[ro.boot.hardware.sku]: [G020N]
[ro.boot.hardware.ufs]: [128GB,Toshiba]
[ro.boot.keymaster]: [1]
[ro.boot.memcg]: [1]
[ro.boot.revision]: [MP1.0]
[ro.boot.secure_boot]: [PRODUCTION]
[ro.boot.serialno]: [9A281FFAZ000SY]
[ro.boot.slot_suffix]: [_a]
[ro.boot.vbmeta.avb_version]: [1.1]
[ro.boot.vbmeta.device_state]: [locked]
[ro.boot.vbmeta.digest]: [40083fd6f6b07c6d95a295aa2609447a835b58f1eae1467418880c1acdbbe835]
[ro.boot.vbmeta.hash_alg]: [sha256]
[ro.boot.vbmeta.size]: [6656]
[ro.boot.verifiedbootstate]: [green]
[ro.boot.veritymode]: [enforcing]
[ro.boot.wificountrycode]: [00]
[ro.bootimage.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.bootimage.build.date.utc]: [1566918137]
[ro.bootimage.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.bootloader]: [c2f2-0.2-5799621]
[ro.bootmode]: [unknown]
[ro.build.ab_update]: [true]
[ro.build.characteristics]: [nosdcard]
[ro.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.build.date.utc]: [1566918137]
[ro.build.description]: [flame-user 10 QD1A.190821.007 5831595 release-keys]
[ro.build.display.id]: [QD1A.190821.007]
[ro.build.expect.baseband]: [g8150-00014-190826-B-5830341]
[ro.build.expect.bootloader]: [c2f2-0.2-5799621]
[ro.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.build.flavor]: [flame-user]
[ro.build.host]: [abfarm799]
[ro.build.id]: [QD1A.190821.007]
[ro.build.product]: [flame]
[ro.build.tags]: [release-keys]
[ro.build.type]: [user]
[ro.build.user]: [android-build]
[ro.build.version.all_codenames]: [REL]
[ro.build.version.base_os]: []
[ro.build.version.codename]: [REL]
[ro.build.version.incremental]: [5831595]
[ro.build.version.min_supported_target_sdk]: [23]
[ro.build.version.preview_sdk]: [0]
[ro.build.version.preview_sdk_fingerprint]: [REL]
[ro.build.version.release]: [10]
[ro.build.version.sdk]: [29]
[ro.build.version.security_patch]: [2019-10-05]
[ro.carrier]: [unknown]
[ro.com.android.dataroaming]: [false]
[ro.com.android.prov_mobiledata]: [false]
[ro.com.google.clientidbase]: [android-google]
[ro.com.google.ime.bs_theme]: [true]
[ro.com.google.ime.height_ratio]: [1.2]
[ro.com.google.ime.system_lm_dir]: [/product/usr/share/ime/google/d3_lms]
[ro.com.google.ime.theme_id]: [5]
[ro.config.alarm_alert]: [Bright_morning.ogg]
[ro.config.low_ram]: [false]
[ro.config.media_vol_steps]: [25]
[ro.config.notification_sound]: [Popcorn.ogg]
[ro.config.ringtone]: [The_big_adventure.ogg]
[ro.config.vc_call_vol_steps]: [7]
[ro.control_privapp_permissions]: [enforce]
[ro.cp_system_other_odex]: [1]
[ro.crypto.state]: [encrypted]
[ro.crypto.type]: [file]
[ro.dalvik.vm.native.bridge]: [0]
[ro.debuggable]: [0]
[ro.device_owner]: [false]
[ro.error.receiver.system.apps]: [com.google.android.gms]
[ro.frp.pst]: [/dev/block/bootdevice/by-name/frp]
[ro.hardware]: [flame]
[ro.hardware.egl]: [adreno]
[ro.hardware.keystore_desede]: [true]
[ro.hardware.vulkan]: [adreno]
[ro.hwui.use_vulkan]: []
[ro.iorapd.enable]: [false]
[ro.llkd.enable]: [false]
[ro.lmk.kill_heaviest_task]: [true]
[ro.lmk.kill_timeout_ms]: [100]
[ro.lmk.log_stats]: [true]
[ro.lmk.use_minfree_levels]: [true]
[ro.logd.size.stats]: [64K]
[ro.minui.pixel_format]: [RGBX_8888]
[ro.odm.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.odm.build.date.utc]: [1566918137]
[ro.odm.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.oem.key1]: [G020N]
[ro.oem_unlock_supported]: [1]
[ro.opa.eligible_device]: [true]
[ro.opengles.version]: [196610]
[ro.postinstall.fstab.prefix]: [/product]
[ro.product.board]: [flame]
[ro.product.brand]: [google]
[ro.product.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.product.build.date.utc]: [1566918137]
[ro.product.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.product.build.id]: [QD1A.190821.007]
[ro.product.build.tags]: [release-keys]
[ro.product.build.type]: [user]
[ro.product.build.version.incremental]: [5831595]
[ro.product.build.version.release]: [10]
[ro.product.build.version.sdk]: [29]
[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.cpu.abilist]: [arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [arm64-v8a]
[ro.product.device]: [flame]
[ro.product.first_api_level]: [29]
[ro.product.locale]: [en-US]
[ro.product.manufacturer]: [Google]
[ro.product.model]: [Pixel 4]
[ro.product.name]: [flame]
[ro.product.odm.brand]: [google]
[ro.product.odm.device]: [flame]
[ro.product.odm.manufacturer]: [Google]
[ro.product.odm.model]: [Pixel 4]
[ro.product.odm.name]: [flame]
[ro.product.product.brand]: [google]
[ro.product.product.device]: [flame]
[ro.product.product.manufacturer]: [Google]
[ro.product.product.model]: [Pixel 4]
[ro.product.product.name]: [flame]
[ro.product.system.brand]: [google]
[ro.product.system.device]: [generic]
[ro.product.system.manufacturer]: [Google]
[ro.product.system.model]: [mainline]
[ro.product.system.name]: [mainline]
[ro.product.vendor.brand]: [google]
[ro.product.vendor.device]: [flame]
[ro.product.vendor.manufacturer]: [Google]
[ro.product.vendor.model]: [Pixel 4]
[ro.product.vendor.name]: [flame]
[ro.property_service.version]: [2]
[ro.qti.sdk.sensors.gestures]: [false]
[ro.qti.sensors.amd]: [false]
[ro.qti.sensors.cmc]: [false]
[ro.qti.sensors.dev_ori]: [true]
[ro.qti.sensors.facing]: [false]
[ro.qti.sensors.pedometer]: [false]
[ro.qti.sensors.rmd]: [false]
[ro.qti.sensors.scrn_ortn]: [false]
[ro.qti.sensors.step_counter]: [false]
[ro.qti.sensors.step_detector]: [false]
[ro.qti.sensors.wu]: [false]
[ro.revision]: [MP1.0]
[ro.secure]: [1]
[ro.serialno]: [9A281FFAZ000SY]
[ro.setupwizard.enterprise_mode]: [1]
[ro.setupwizard.esim_cid_ignore]: [00000001]
[ro.setupwizard.rotation_locked]: [true]
[ro.sf.lcd_density]: [440]
[ro.storage_manager.enabled]: [false]
[ro.storage_manager.show_opt_in]: [false]
[ro.surface_flinger.display_primary_blue]: [84.46,34.06,512.39]
[ro.surface_flinger.display_primary_green]: [109.31,315.96,14.97]
[ro.surface_flinger.display_primary_red]: [263.34,120.63,0.21]
[ro.surface_flinger.display_primary_white]: [434.54,455.62,502.86]
[ro.surface_flinger.has_HDR_display]: [true]
[ro.surface_flinger.has_wide_color_display]: [true]
[ro.surface_flinger.protected_contents]: [true]
[ro.surface_flinger.set_display_power_timer_ms]: [1000]
[ro.surface_flinger.set_idle_timer_ms]: [80]
[ro.surface_flinger.set_touch_timer_ms]: [200]
[ro.surface_flinger.support_kernel_idle_timer]: [true]
[ro.surface_flinger.use_color_management]: [true]
[ro.surface_flinger.use_smart_90_for_video]: [true]
[ro.surface_flinger.vsync_event_phase_offset_ns]: [2000000]
[ro.surface_flinger.vsync_sf_event_phase_offset_ns]: [6000000]
[ro.surface_flinger.wcg_composition_dataspace]: [143261696]
[ro.sys.sdcardfs]: [1]
[ro.system.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.system.build.date.utc]: [1566918137]
[ro.system.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.system.build.id]: [QD1A.190821.007]
[ro.system.build.tags]: [release-keys]
[ro.system.build.type]: [user]
[ro.system.build.version.incremental]: [5831595]
[ro.system.build.version.release]: [10]
[ro.system.build.version.sdk]: [29]
[ro.telephony.default_cdma_sub]: [0]
[ro.telephony.default_network]: [10]
[ro.treble.enabled]: [true]
[ro.url.legal]: [http://www.google.com/intl/%s/mobile/android/basic/phone-legal.html]
[ro.url.legal.android_privacy]: [http://www.google.com/intl/%s/mobile/android/basic/privacy.html]
[ro.vendor.build.date]: [Tue Aug 27 15:02:17 UTC 2019]
[ro.vendor.build.date.utc]: [1566918137]
[ro.vendor.build.fingerprint]: [google/flame/flame:10/QD1A.190821.007/5831595:user/release-keys]
[ro.vendor.build.security_patch]: [2019-10-05]
[ro.vndk.version]: [29]
[ro.wifi.channels]: []
[ro.zram.first_wb_delay_mins]: [180]
[ro.zram.mark_idle_delay_mins]: [60]
[ro.zram.periodic_wb_delay_hours]: [24]
[ro.zygote]: [zygote64_32]
[security.perf_harden]: [1]
[selinux.restorecon_recursive]: [/data/misc_ce/0]
[service.bootanim.exit]: [1]
[service.sf.present_timestamp]: [1]
[setupwizard.enable_assist_gesture_training]: [true]
[setupwizard.feature.baseline_setupwizard_enabled]: [true]
[setupwizard.feature.show_pai_screen_in_main_flow.carrier1839]: [false]
[setupwizard.feature.show_pixel_tos]: [true]
[setupwizard.feature.show_support_link_in_deferred_setup]: [false]
[setupwizard.feature.skip_button_use_mobile_data.carrier1839]: [true]
[setupwizard.theme]: [glif_v3_light]
[sys.boot.reason]: [reboot,userrequested]
[sys.boot.reason.last]: [reboot,userrequested]
[sys.boot_completed]: [1]
[sys.isolated_storage_snapshot]: [true]
[sys.logbootcomplete]: [1]
[sys.oem_unlock_allowed]: [0]
[sys.retaildemo.enabled]: [0]
[sys.sysctl.extra_free_kbytes]: [28856]
[sys.system_server.start_count]: [1]
[sys.system_server.start_elapsed]: [4580]
[sys.system_server.start_uptime]: [4580]
[sys.usb.config]: [adb]
[sys.usb.configfs]: [2]
[sys.usb.controller]: [a600000.dwc3]
[sys.usb.ffs.ready]: [1]
[sys.usb.mtp.device_type]: [3]
[sys.use_memfd]: [false]
[sys.user.0.ce_available]: [true]
[sys.wifitracing.started]: [1]
[telephony.lteOnCdmaDevice]: [1]
[vendor.display.native_display_primaries_ready]: [1]
[vendor.display.primary_blue]: [84.46,34.06,512.39]
[vendor.display.primary_green]: [109.31,315.96,14.97]
[vendor.display.primary_red]: [263.34,120.63,0.21]
[vendor.display.primary_white]: [434.54,455.62,502.86]
[vidc.enc.dcvs.extra-buff-count]: [2]
[vidc.enc.disable.pq]: [1]
[vold.has_adoptable]: [0]
[vold.has_quota]: [1]
[vold.has_reserved]: [1]
[wifi.interface]: [wlan0]

Google Pixel 4 (QD1A.190921.007) Feature 一覧

また確認したくなることがあるかもしれないので、ここに貼っておきます。

$ pm list features

feature:reqGlEsVersion=0x30002
feature:android.hardware.audio.low_latency
feature:android.hardware.audio.output
feature:android.hardware.audio.pro
feature:android.hardware.biometrics.face
feature:android.hardware.bluetooth
feature:android.hardware.bluetooth_le
feature:android.hardware.camera
feature:android.hardware.camera.any
feature:android.hardware.camera.autofocus
feature:android.hardware.camera.capability.manual_post_processing
feature:android.hardware.camera.capability.manual_sensor
feature:android.hardware.camera.capability.raw
feature:android.hardware.camera.flash
feature:android.hardware.camera.front
feature:android.hardware.camera.level.full
feature:android.hardware.faketouch
feature:android.hardware.location
feature:android.hardware.location.gps
feature:android.hardware.location.network
feature:android.hardware.microphone
feature:android.hardware.nfc
feature:android.hardware.nfc.any
feature:android.hardware.nfc.ese
feature:android.hardware.nfc.hce
feature:android.hardware.nfc.hcef
feature:android.hardware.nfc.uicc
feature:android.hardware.opengles.aep
feature:android.hardware.ram.normal
feature:android.hardware.screen.landscape
feature:android.hardware.screen.portrait
feature:android.hardware.sensor.accelerometer
feature:android.hardware.sensor.assist
feature:android.hardware.sensor.barometer
feature:android.hardware.sensor.compass
feature:android.hardware.sensor.gyroscope
feature:android.hardware.sensor.hifi_sensors
feature:android.hardware.sensor.light
feature:android.hardware.sensor.proximity
feature:android.hardware.sensor.stepcounter
feature:android.hardware.sensor.stepdetector
feature:android.hardware.strongbox_keystore
feature:android.hardware.telephony
feature:android.hardware.telephony.carrierlock
feature:android.hardware.telephony.cdma
feature:android.hardware.telephony.euicc
feature:android.hardware.telephony.gsm
feature:android.hardware.telephony.ims
feature:android.hardware.touchscreen
feature:android.hardware.touchscreen.multitouch
feature:android.hardware.touchscreen.multitouch.distinct
feature:android.hardware.touchscreen.multitouch.jazzhand
feature:android.hardware.usb.accessory
feature:android.hardware.usb.host
feature:android.hardware.vulkan.compute
feature:android.hardware.vulkan.level=1
feature:android.hardware.vulkan.version=4198400
feature:android.hardware.wifi
feature:android.hardware.wifi.aware
feature:android.hardware.wifi.direct
feature:android.hardware.wifi.passpoint
feature:android.hardware.wifi.rtt
feature:android.software.activities_on_secondary_displays
feature:android.software.app_widgets
feature:android.software.autofill
feature:android.software.backup
feature:android.software.cant_save_state
feature:android.software.companion_device_setup
feature:android.software.connectionservice
feature:android.software.cts
feature:android.software.device_admin
feature:android.software.device_id_attestation
feature:android.software.file_based_encryption
feature:android.software.home_screen
feature:android.software.input_methods
feature:android.software.ipsec_tunnels
feature:android.software.live_wallpaper
feature:android.software.managed_users
feature:android.software.midi
feature:android.software.picture_in_picture
feature:android.software.print
feature:android.software.secure_lock_screen
feature:android.software.securely_removes_users
feature:android.software.sip
feature:android.software.sip.voip
feature:android.software.verified_boot
feature:android.software.voice_recognizers
feature:android.software.webview
feature:com.google.android.apps.dialer.SUPPORTED
feature:com.google.android.apps.photos.PIXEL_2019_PRELOAD
feature:com.google.android.feature.DREAMLINER
feature:com.google.android.feature.EXCHANGE_6_2
feature:com.google.android.feature.GOOGLE_BUILD
feature:com.google.android.feature.GOOGLE_EXPERIENCE
feature:com.google.android.feature.NEXT_GENERATION_ASSISTANT
feature:com.google.android.feature.PIXEL_2017_EXPERIENCE
feature:com.google.android.feature.PIXEL_2018_EXPERIENCE
feature:com.google.android.feature.PIXEL_2019_EXPERIENCE
feature:com.google.android.feature.PIXEL_2019_MIDYEAR_EXPERIENCE
feature:com.google.android.feature.PIXEL_EXPERIENCE
feature:com.google.android.feature.TURBO_PRELOAD
feature:com.google.android.feature.WELLBEING
feature:com.google.android.feature.ZERO_TOUCH
feature:com.verizon.hardware.telephony.ehrpd
feature:com.verizon.hardware.telephony.lte

BER-TLV クラスを Kotlin で書いてみる

仕様のおさらい

T (タグ) フィールドは 3 バイトまで

ITU-T X.690 や ISO/IEC 8825-1 の規定では T/L/V 各フィールドの長さに関する自由度が高く、著しく長いタグやデータ長を表現することも可能です。ISO/IEC 7816 の現在の運用ではタグの長さを 3 バイトまでとしているようなので、今回はそれに従います。簡潔に書くと、こんなルールになっています。

  • バイト 1 の下位 5 ビットの何れかが落ちていれば 1 バイトタグ
  • バイト 2 の最上位ビットが落ちていれば 2 バイトタグ
  • バイト 3 の最上位ビットが落ちていれば 3 バイトタグ
L (レングス) フィールドは 4 バイトまで

L フィールドの長さは、ETSI TS 101 220 の 7.1.2 Length encoding の規定に従って 4 バイトまでとします。バイト 1 の最上位が立っているときの残りビットで後続のバイト長を表現しますが、それを 3 まで ('83') 許容することになります。Definite フォームのみをサポートし、Indefinite フォームは考慮しません。

Length Byte 1 Byte 2:4
0 to 127 Length ('00' to '7F') N/A
128 to 255 '81' Length ('80' to 'FF')
256 to 65535 '82' Length ('01 00' to 'FF FF')
65536 to 16777215 '83' Length ('01 00 00' to 'FF FF FF')
V (バリュー) フィールドは 2 種類

データの本体が入るこのフィールドには、Primitive なデータの他、複数の BER-TLV 形式のデータを入れることができます。タグ 1 バイト目の上から 3 ビット目がセットされていれば、BER-TLV 形式のデータが入っていることがわかります。BER-TLV 形式のデータの中に更に BER-TLV 形式のデータが入り、更にその中に .. と入れ子構造を続けることが可能です。

Bit Data Type
0 Primitive
1 Constructed

実際に作ってみる

絵にする

Kotlin のコードは、UML ではどのように表現するのが正しいんでしょう。実際のコードには getTag() や getValue() といったゲッター等は登場しないのですが、それらを登場させて Java で書くとするとこんな感じになりますでしょうか。

f:id:cheerio-the-bear:20200503191445p:plain
BerTlv クラス

いずれ COMPREHENSION-TLV 等の別の TLV 形式も取り扱うかもしれないですし、Tlv クラスと BerTlv クラスを分けています。どちらもコンストラクタは隠していて、インスタンスの生成には Static で用意した BerTlv.listFrom() を使います。バイト配列を放り込めば、BerTlv クラスのインスタンスが数珠つなぎになった状態で生成される算段です。isConstructed である場合には、Value を TLV 配列の形式でも提供します。

上の絵は、PlantUML で下記のように書いています。

@startuml

skinparam classAttributeIconSize 0

package util {

    class Tlv {
        - tag: Int
        - isConstructed: Boolean
        - primitiveValue: ByteArray
        - tlvs: List<Tlv>
        --
        <<create>> #Tlv(tag: Int, valueArg: ByteArray)
        ..
        + getTag(): Int
        + getValue(): ByteArray
        + getTlvs(): List<Tlv>
        ..
        {abstract} + isConstructed(): Boolean
        {abstract} + listFrom(bytes: ByteArray): List<Tlv>
        {abstract} + toByteArray(): ByteArray
    }

    class BerTlv {
        {static} + listFrom(bytes: ByteArray): List<Tlv>
        ..
        <<create>> -BerTlv(tag: Int, valueArg: ByteArray)
        ..
        + isConstructed(): Boolean
        + listFrom(bytes: ByteArray): List<Tlv>
        + toByteArray(): ByteArray
    }
}

class List

BerTlv -right-|> Tlv
List o-right- "    0..*" Tlv
List --* Tlv

@enduml
コードにする

実際に書いてみたコードは、こちらのコミットにあります。

github.com

テストコードを引用します。このテストコードでは、複数の階層に渡る BER-TLV 構造を今回作成したクラスにデコードさせています。

    @Test
    fun constructed() {
        /*
           |  T | 21 | (1) A constructed BER-TLV contains a constructed one and a primitive one
           |  L | 0A |
           |  V |  T | 22 | (2) A constructed BER-TLV contains a primitive one and a constructed one
           |    |  L | 05 |
           |    |  V |  T | 01 | (3) A primitive BER-TLV
           |    |    |  L | 01 |
           |    |    |  V | 01 |
           |    |    |  T | 21 | (4) A constructed BER-TLV with no value
           |    |    |  L | 00 |
           |    |    |  V | -- |
           |    |  T | 02 | (5) A primitive BER-TLV
           |    |  L | 01 |
           |    |  V | 01 |
         */
        val input = hexStringToByteArray("210A22050101012100020101")
        val tlvs = BerTlv.listFrom(input)

        assertThat(tlvs.size).isEqualTo(1)

        // (1) A constructed BER-TLV contains a constructed one and a primitive one
        assertThat(tlvs[0].tag).isEqualTo(0x21)
        assertThat(tlvs[0].isConstructed).isTrue()
        assertThat(tlvs[0].value).isEqualTo(hexStringToByteArray("22050101012100020101"))
        assertThat(tlvs[0].toByteArray()).isEqualTo(
                hexStringToByteArray("210A22050101012100020101"))
        assertThat(tlvs[0].tlvs.size).isEqualTo(2)

        // (2) A constructed BER-TLV contains a primitive one and a constructed one
        assertThat(tlvs[0].tlvs[0].tag).isEqualTo(0x22)
        assertThat(tlvs[0].tlvs[0].isConstructed).isTrue()
        assertThat(tlvs[0].tlvs[0].value).isEqualTo(hexStringToByteArray("0101012100"))
        assertThat(tlvs[0].tlvs[0].toByteArray()).isEqualTo(
                hexStringToByteArray("22050101012100"))
        assertThat(tlvs[0].tlvs[0].tlvs.size).isEqualTo(2)

        // (3) A primitive BER-TLV
        assertThat(tlvs[0].tlvs[0].tlvs[0].tag).isEqualTo(0x01)
        assertThat(tlvs[0].tlvs[0].tlvs[0].isConstructed).isFalse()
        assertThat(tlvs[0].tlvs[0].tlvs[0].value).isEqualTo(hexStringToByteArray("01"))
        assertThat(tlvs[0].tlvs[0].tlvs[0].toByteArray()).isEqualTo(
                hexStringToByteArray("010101"))

        // (4) A constructed BER-TLV with no value
        assertThat(tlvs[0].tlvs[0].tlvs[1].tag).isEqualTo(0x21)
        assertThat(tlvs[0].tlvs[0].tlvs[1].isConstructed).isTrue()
        assertThat(tlvs[0].tlvs[0].tlvs[1].value).isEqualTo(byteArrayOf())
        assertThat(tlvs[0].tlvs[0].tlvs[1].toByteArray()).isEqualTo(
                hexStringToByteArray("2100"))

        // (5) A primitive BER-TLV
        assertThat(tlvs[0].tlvs[1].tag).isEqualTo(0x02)
        assertThat(tlvs[0].tlvs[1].isConstructed).isFalse()
        assertThat(tlvs[0].tlvs[1].value).isEqualTo(hexStringToByteArray("01"))
        assertThat(tlvs[0].tlvs[1].toByteArray()).isEqualTo(
                hexStringToByteArray("020101"))
    }

BER-TLV のエンコードも見据えて数珠つなぎ構造で考えてはみたものの、デコードする機能だけを持たせるに留めています。各要素の内容や長さ、順番等に関する制限はそれぞれの実際の運用によることもあり、それらをこのレベルで全て考慮するのはあまり得策ではないと思うに至りました。既に存在する BER-TLV 要素の中身の編集や削除は容易ですが、新たな BER-TLV 要素の挿入は厄介そうではないですか。もう一段上のレベル、アプリケーション寄りのところにエンコードを可能にするクラス群を(いつか)設けることにします。

前後の記事というか日記

普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。

前回の記事というか日記はこちら。

cheerio-the-bear.hatenablog.com

Continuation による AssertionError で MockK に乗り換える

Repository 以下はだいたいこんな感じ

他の人が書かれた Kotlin のコードを見ながら、適当にそれらしく書いてみています。そんな調子でもそこそこちゃんと動いてくれるので、Kotlin の基礎みたいなところを全く習得できていないような気はします。

Android の TelephonyManager API を使って SIM カードに対して APDU コマンドの送受信を行う TelephonyInterface クラスを実装した後、Repository パッケージと Cache I/O パッケージを追加して(いろいろ端折っていますが)こんな感じになっています。

f:id:cheerio-the-bear:20200418233424p:plain
Repository / DataStores

Cache I/O パッケージ (link)

Room も今回初めて使ってみたのですが、データベース制御的なクラスがあまりに簡単に書けてしまうことにちょっとびっくりです。テンプレート的なものに適当な SQL 文を書き添えるだけで出来てしまうなんて。

f:id:cheerio-the-bear:20200418233636p:plain
DataStore (Cache I/O)

SIM アプリケーション上にある各種 EF/DF の FCP テンプレートは、おそらくそう簡単に変わるものではないでしょう。ファイルツリーを走査したときに得られる FCP テンプレートだけ、カードの ICCID と紐付けて ”SelectResponse” データベースに残すようにしようと考えています。ファイルツリーの走査を終えたカードの ICCID は "CachedSubscription" データベースに書き、キャッシュ処理を完了していることを覚えておくようにしようと思います。

Repository パッケージ (link)

Repository を通して送信できる APDU コマンドは、この時点では SELECT と READ BINARY、そして READ RECORD だけです。いずれ VERIFY PIN や UPDATE BINARY 等も必要になりますが、また折を見て追加してゆこうと思います。

f:id:cheerio-the-bear:20200418233615p:plain
Repository

Android のテレフォニーフレームワークの中に ApduSender というクラスがいるのですが、ほんの幾つかの APDU コマンドを投げただけで(というかおそらく現状実装では STORE DATA コマンドばかりなので概ね 1 コマンド毎に)論理チャネルをクローズしてしまいます。それに倣うかたちで設計すると論理チャネルのオープン・クローズを頻繁に発生させてしまいそうなので、ひとまずクローズ条件を「500ms タイマーをかけている間に後続のリクエストが来ない場合」にしておきます。

Mockito から MockK に乗り換えました

Mockito を使って CardRepository クラスのテストコードを書き始めたのですが、Suspend 関数の呼び出しを Verify しようとすると下記のエラーが発生しました。どうやら、Kotlin によって暗黙的に追加されている第二引数の Continuation の関係でアサートに失敗しているようですが、簡単に解決できそうな方法を速やかに見つけることができず。

java.lang.AssertionError: Verification failed: call 1 of 1:
SelectResponseDataSource(#2).insert(eq(
    SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[-104, -120, 18, 1, 0, 0, 32, 40, 1, -10], sw=36864)), any())).
    Only one matching call to
    SelectResponseDataSource(#2)/insert(SelectResponse, Continuation) happened, but arguments are not matching:

[0]: argument: SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[98, 30, -126, 2, 65, 33, -125, 2, 47, -30, -91, 6, -64, 1, 0, -54, 1, -128, -118, 1, 5, -117, 3, 47, 6, 4, -128, 2, 0, 10, -120, 0], sw=36864), matcher: eq(SelectResponse(iccId=8988211000000282106F, aid=, path=, fileId=2FE2, data=[-104, -120, 18, 1, 0, 0, 32, 40, 1, -10], sw=36864)), result: -
[1]: argument: continuation {}, matcher: any(), result: +

Kotlin の Coroutines にも対応していると書かれていた MockK に移行し、テストコードの続きを書きました。下記コードに登場するモックたちの関数は全て Suspend 関数なのですが、Verify も問題なく動いてくれています。今後のテストコードは、MockK を使って書くようにします。

    @Test
    fun initialize_notCached() = runBlocking {
        coEvery { subscriptionIoMock.get(ICCID) } returns null
        coEvery { cacheIoMock.delete(any()) } answers { nothing }
        coEvery { cacheIoMock.insert(any()) } answers { nothing }

        assertThat(repository.initialize()).isTrue()
        assertThat(repository.isAccessible).isTrue()
        assertThat(repository.isCached).isFalse()

        // Query is not yet available before finishing the caching operation.
        assertThat(repository.queryFileControlParameters(LEVEL_ADF)).isEmpty()

        coVerifyOrder {
            cacheIoMock.delete(ICCID)
            cacheIoMock.insert(SelectResponse(ICCID, FileId.AID_NONE,
                    FileId.PATH_MF, CardRepository.EF_ICCID,
                    hexStringToByteArray(FCP), Result.SW_NORMAL))
        }
    }

MockK を導入したときのコミットはこちら。

github.com

本家サイトの説明と、その日本語訳まとめページも参考にさせて頂いています(ありがとうございます)。

mockk.io
qiita.com

前後の記事というか日記

普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。

前回の記事というか日記はこちら。

cheerio-the-bear.hatenablog.com

次回はこちらです。

cheerio-the-bear.hatenablog.com

楽天モバイル MNO SIM カードは読み物です

[2020/04/11 追記] EF DIR の二つ目のレコードを見落としていたので訂正。
[2020/04/11 追記] Pixel 3 でうまく開通できていない状況をメモ。

開通できません

先日契約した Rakuten MNO の SIM カードが届いたのですが、どうやら自宅にある各種スマホタブレットに装着しても開通手続きが実行されず、EF MSISDN が空のままです。昨今の問題で外出する機会も減少し、そもそもこの契約必要だったんだっけ的な状況になっていることもあり、開通用に新しい端末を購入する気にもなれません。

[2020/04/11 追記] Google ストアで購入した Pixel 3 で試した結果をメモします。

  • LTE only モードに設定することで、データ通信は可能。
  • データ側は 440/53 で在圏、音声側がサーチ状態のまま。
  • 周囲に楽天モバイル網 440/11 はなし。
  • EF MSISDN に電話番号が入らない。

仕方がないので、カード上にある情報の中から特に定番なところを読んでみました。SIM カードは読み物です。

ATR

ATR は、Android 端末が出力してくれる radio ログが読みやすいです。論理チャネルの数が 8 本以上あるそうです。Android はシステム起動時に論理チャネルを欲しがる人が増えてきているので、数が多いのは良いですね。

04-08 19:41:12.237 D/AnswerToReset( 2193):
    Successfully parsed the ATR string 3b9f96c00a1fc68031e073fe211f65d00233131b810ffa
    into AnswerToReset:{mConventionByte=3B,mFormatByte=9F,
    mInterfaceBytes={{TA=96,TB=null,TC=null,TD=C0}
                     {TA=null,TB=null,TC=0A,TD=1F}
                     {TA=C6,TB=null,TC=null,TD=null}},
    mHistoricalBytes={80,31,E0,73,FE,21,1F,65,D0,02,33,13,1B,81,0F,},
    mCheckByte=FA}

EF DIR (File ID: 2F00)

[2020/04/11 訂正] レコードが二つあるのを見落としてました。一つ目は USIM ADF。

AID Value #1 A0 00 00 00 87 10 02 FF 81 FF 09 89 09 06 00 00 USIM ADF
Application Label #1 52 61 6B 75 74 65 6E Rakuten

二つ目は ISIM ADF で、どちらもラベルは ”Rakuten" で同じ。

AID Value #2 A0 00 00 00 87 10 04 FF 81 FF 09 89 08 00 01 00 ISIM ADF
Application Label #2 52 61 6B 75 74 65 6E Rakuten

EF SPN (USIM ADF / File ID: 6F46)

ホーム網またはそれと同様の扱いの網に在圏している場合には、在圏網の PLMN を表示しないよう指示されています。

Display Condition 02
Service Provider Name 52616B7574656E Rakuten

EF UST (USIM ADF / File ID: 6F38)

最近のリリースで定義されているテーブルより、ちょっぴり短いです。

Services 006E9C0D2136040040031084

これも、Android radio ログに出力されているのを読むのが楽です。

04-08 19:41:12.886 D/SIMRecords( 2193): [SIMRecords] SST: UsimServiceTable[96]={
    SM_STORAGE, SM_STATUS_REPORTS, SM_SERVICE_PARAMS, CAP_CONFIG_PARAMS_2,
    CB_MESSAGE_ID, SPN, USER_PLMN_SELECT, MSISDN, EMLPP, EMLPP_AUTO_ANSWER,
    GSM_ACCESS, DATA_DL_VIA_SMS_PP, IGNORED_1, GSM_SECURITY_CONTEXT,
    OPERATOR_PLMN_SELECT, HPLMN_SELECT, PLMN_NETWORK_NAME, OPERATOR_PLMN_LIST,
    MWI_STATUS, SERVICE_PROVIDER_DISPLAY_INFO, EQUIVALENT_HPLMN,
    EQUIVALENT_HPLMN_PRESENTATION, LAST_RPLMN_SELECTION_INDICATION,
    EPS_MOBILITY_MANAGEMENT_INFO, SM_OVER_IP, NAS_CONFIG_BY_USIM }

EF ECC (USIM ADF / File ID: 6FB7)

順序はさておき、定番の三種類でした。

Record #1 11 F8 FF 08 118 (Service Category: Marine Guard)
Record #2 11 F0 FF 01 110 (Service Category: Police)
Record #3 11 F9 FF 06 119 (Service Category: Fire Brigade + Ambulance)

EF PNN (USIM ADF / File ID: 6FC5)

フルネームとショートネームが同じ "Rakuten" で、しかもそっくり同じレコードが何故か二つ並んでいます。何か意図があるんでしょうか(きっと無い)。

Record #1 43 08 87 D2 F0 BA 4E 2F BB 01 45 08 87 D2 F0 BA 4E 2F BB 01 Rakuten (Full/Short)
Record #2 43 08 87 D2 F0 BA 4E 2F BB 01 45 08 87 D2 F0 BA 4E 2F BB 01 Rakuten (Full/Short)

EF OPL (USIM ADF / File ID: 6FC6)

440/11 網の名前には PNN レコード #1 の "Rakuten" を、440/53 網の名前には PNN レコード #2 の "Rakuten" を参照するよう指示しています。何か意図があるんでしょうか(きっと無い)。このレコード #2 が無ければパートナーエリアでの "Rakuten" 表示を回避できそうですが、残念ながら UPDATE や DEACTIVATE には ADM キーが必要です。

Record #1 44 F0 11 00 00 FF FE 01 440/11 全範囲で PNN Record #1 を参照
Record #2 44 F0 35 00 00 FF FE 02 440/53 全範囲で PNN Record #2 を参照

EF SPDI (USIM ADF / File ID: 6FCD)

ここでも 440/53 をホーム網として取り扱うよう指示されています。

PLMN #1 44 F0 11 440/11
PLMN #2 44 F0 35 440/53

APDU コマンド出ました

今回は学習目的で「しっかりテストコードを書く」と決めて始めてしまったので、ちょっとしたことを実現するだけでもとても時間がかかります。おかげで、Kotlin の超基本的な文法みたいなところはちょっと書き慣れてきたような気がしないでもないです。

MF を SELECT (一回目)

ということで、APDU コマンドの送受信のために最低限必要となるコードを書いて、基本チャネルを使って MF に対して SELECT コマンドを送信してみました。動作確認に使ったスマートフォンが出力する radio ログを見てみると、ちゃんと期待通りの RIL コマンドとその応答が表示されています。

[0146]> RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC [SUB0]
[0146]< RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC IccIoResult sw1:0x61 sw2:0x56 Payload: ******* Error: unknown [SUB0]

良かった良かった ... いや、SELECT は出来てそうですけど SW 61xx じゃないですか。なるほど、SW 61xx のハンドリングはアプリ側でやってあげないといけなかったんですね。確かにそんな感じです。

github.com

MF を SELECT (二回目)

SW 61xx に加えて SW 6Cxx についても考慮するようコードを書き換えて、今度は送信したコマンドと受信したデータの両方を自前でログに出力してみました。

V/TelephonyInterface0: Sent: 00A40004023F00
V/TelephonyInterface0: Received: 6156
V/TelephonyInterface0: Sent: 00C0000056
V/TelephonyInterface0: Received: 62548202782183023F00A51980017183027FFFCB0D00000000000000000000000000CA01828A0105AB1B84012E9000840188A4068301019501088401FCA40683010A950108C60F90017083010183010A83010B8301819000

いいですね。ターゲットを MF (3F00) にした SELECT (A4) コマンドが送信され、SW 6156 を受けて GET RESPONSE (C0) コマンドを送信。データが返ってきて最後は SW 9000 で終了。ちゃんと送受信できてそうです。

github.com

ここまでのクラス構成

TelephonyManager の iccTransmitApduBasicChannel() や iccTransmitApduLogicalChannel() には既に "Deprecated in API level R" が付けられていますが、その代わりに利用することが推奨されている android.se.omapi への移行は Android R 端末を入手してからです。そのため、今回はそれらの TelephonyManager API を使うことにはしましたが、APDU コマンドやその応答は 16 進数の String 表現ではなく android.se.omapi を想定してByteArray で表現するようにしました。 無駄にデータのコンバートが発生しますが、やむを得ませんね。

f:id:cheerio-the-bear:20200320120120p:plain
ここまでのクラス構成

今後は、クラス図中央上にいる TelephonyInterface クラスを使って APDU コマンドを送信してゆきます。チャネルリソースには限りがあることですし、基本チャネルもしくはその他の論理チャネルの中から一本だけを同時に利用できる仕様にしています。

ところでこの TelephonyManager API ですが、誰でも自由に使えるものではありません。久しぶりにテスト SIM の ARA-M を改変し、作成中のアプリに UICC Carrier Privileges を与えることにしました。

cheerio-the-bear.hatenablog.com

関連のある記事というか日記

普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。

前回の記事というか日記はこちら。

cheerio-the-bear.hatenablog.com

次の回はこちらです。

cheerio-the-bear.hatenablog.com

Mockito や Shadow を使ってテストコードを書くのは楽しい

Kotlin でコード書くのはとても楽しいですし、Mockito や Shadow を使ってテストコードを書くのも楽しいですね。業務で携わっているコードにはテストコードがあまり用意されていないものもありますので、このように自分で書いてみるのはとても有意義です。しばらく使わないとすぐに忘れてしまうでしょうから、今回のテストコードを書く際に使ったものたちについて簡単にメモしておこうと思います。

github.com

テストコードにパーミッションを与える

今回のテストの対象としたクラスでは、指定した SIM カードスロットに対応するアクティブな SubscriptionInfo を取得するために、SubscriptionManager の getActiveSubscriptionInfoForSimSlotIndex() を呼んでいます。API の実行には READ_PHONE_STATE パーミッションが必要になるので、テストコードには GrantPermissionRule を使って同パーミッションを与えました。

    @Rule @JvmField
    val grantPermissionRule: GrantPermissionRule =
            GrantPermissionRule.grant(android.Manifest.permission.READ_PHONE_STATE)

Kotlin コード上で機能させるために、@JvmField アノテーションを付けています。

developer.android.com

ShadowSubscriptionManager に SubscriptionInfo のモックを適用する

Android Studio 上でテストコードを実行する際、当然ながらアクティブな SubscriptionInfo はありません。それがある状態を模するために、SubscriptionManager の Shadow (ShadowSubscriptionManager) と Mockito のモックを併用しました。どっちも使ってみたかったんです。

    private val smShadow = shadowOf(context.getSystemService(
            Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager)
    @Mock
    private lateinit var subInfoMock: SubscriptionInfo

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        // Associate the subscription info #TEST_SUBS_ID with the slot #TEST_SLOT_ID.
        `when`(subInfoMock.simSlotIndex).thenReturn(TEST_SLOT_ID)
        `when`(subInfoMock.subscriptionId).thenReturn(TEST_SUBS_ID)
        smShadow.setActiveSubscriptionInfos(subInfoMock)
    ...

これにより、SIM カードスロット #TEST_SUBS_ID に紐づくアクティブな SubscriptionInfo を取得できるようになりました。

ShadowTelephonyManager に TelephonyManager のモックを適用する

Shadow を使わずに全部 Mockito で出来たんじゃないかと思ったりもしますが、いいんです。Shadow も使ってみたかったんです。

    private val tmShadow = shadowOf(context.getSystemService(
            Context.TELEPHONY_SERVICE) as TelephonyManager)
    @Mock
    private lateinit var tmMock: TelephonyManager

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        tmShadow.setTelephonyManagerForSubscriptionId(TEST_SUBS_ID, tmMock)
    ...

これで、#TEST_SUBS_ID 用の TelephonyManager をモックに差し替えることができました。

TelephonyManager のモックに適当な応答を返させる

下記コード例の setUp() では、AID1 または AID2 を指定して論理チャネルを開こうとした場合に、STATUS_NO_ERROR を応答させるようにモックを設定しています。

    @Mock
    private lateinit var respMock: IccOpenLogicalChannelResponse

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
    ...
        // Create the interface for the slot #TEST_SLOT_ID.
        tif = TelephonyInterface.from(context, TEST_SLOT_ID)

        // By default, both AID1 and AID2 are accessible.
        `when`(respMock.channel).thenReturn(TEST_CHANNEL_ID)
        `when`(respMock.status).thenReturn(IccOpenLogicalChannelResponse.STATUS_NO_ERROR)
        `when`(tmMock.iccOpenLogicalChannel(AID1_STRING, TEST_OPEN_P2)).thenReturn(respMock)
        `when`(tmMock.iccOpenLogicalChannel(AID2_STRING, TEST_OPEN_P2)).thenReturn(respMock)
    }

    @Test
    fun openClose_logicalChannel_share() {
        assertThat(tif.openChannel(AID1)).isEqualTo(Interface.OpenChannelResult.SUCCESS)
        assertThat(tif.openChannel(AID1)).isEqualTo(Interface.OpenChannelResult.SUCCESS)
        tif.closeRemainingChannel()

        verify(tmMock, times(1)).iccOpenLogicalChannel(AID1_STRING, TEST_OPEN_P2)
        verify(tmMock, times(1)).iccCloseLogicalChannel(TEST_CHANNEL_ID)
    }

このテストケースでは、iccOpenLogicalChannel() と iccCloseLogicalChannel() が一度ずつ呼び出されていたかどうかをチェックしています。

関連のある記事

普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。

前回の記事はこちら。

cheerio-the-bear.hatenablog.com

次回の記事はこちら。

cheerio-the-bear.hatenablog.com