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 で書くとするとこんな感じになりますでしょうか。
いずれ 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
コードにする
実際に書いてみたコードは、こちらのコミットにあります。
テストコードを引用します。このテストコードでは、複数の階層に渡る 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 要素の挿入は厄介そうではないですか。もう一段上のレベル、アプリケーション寄りのところにエンコードを可能にするクラス群を(いつか)設けることにします。
Continuation による AssertionError で MockK に乗り換える
Repository 以下はだいたいこんな感じ
他の人が書かれた Kotlin のコードを見ながら、適当にそれらしく書いてみています。そんな調子でもそこそこちゃんと動いてくれるので、Kotlin の基礎みたいなところを全く習得できていないような気はします。
Android の TelephonyManager API を使って SIM カードに対して APDU コマンドの送受信を行う TelephonyInterface クラスを実装した後、Repository パッケージと Cache I/O パッケージを追加して(いろいろ端折っていますが)こんな感じになっています。
Cache I/O パッケージ (link)
Room も今回初めて使ってみたのですが、データベース制御的なクラスがあまりに簡単に書けてしまうことにちょっとびっくりです。テンプレート的なものに適当な SQL 文を書き添えるだけで出来てしまうなんて。
SIM アプリケーション上にある各種 EF/DF の FCP テンプレートは、おそらくそう簡単に変わるものではないでしょう。ファイルツリーを走査したときに得られる FCP テンプレートだけ、カードの ICCID と紐付けて ”SelectResponse” データベースに残すようにしようと考えています。ファイルツリーの走査を終えたカードの ICCID は "CachedSubscription" データベースに書き、キャッシュ処理を完了していることを覚えておくようにしようと思います。
Repository パッケージ (link)
Repository を通して送信できる APDU コマンドは、この時点では SELECT と READ BINARY、そして READ RECORD だけです。いずれ VERIFY PIN や UPDATE BINARY 等も必要になりますが、また折を見て追加してゆこうと思います。
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 を導入したときのコミットはこちら。
本家サイトの説明と、その日本語訳まとめページも参考にさせて頂いています(ありがとうございます)。
前後の記事というか日記
普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。
前回の記事というか日記はこちら。
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 のハンドリングはアプリ側でやってあげないといけなかったんですね。確かにそんな感じです。
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 で終了。ちゃんと送受信できてそうです。
ここまでのクラス構成
TelephonyManager の iccTransmitApduBasicChannel() や iccTransmitApduLogicalChannel() には既に "Deprecated in API level R" が付けられていますが、その代わりに利用することが推奨されている android.se.omapi への移行は Android R 端末を入手してからです。そのため、今回はそれらの TelephonyManager API を使うことにはしましたが、APDU コマンドやその応答は 16 進数の String 表現ではなく android.se.omapi を想定してByteArray で表現するようにしました。 無駄にデータのコンバートが発生しますが、やむを得ませんね。
今後は、クラス図中央上にいる TelephonyInterface クラスを使って APDU コマンドを送信してゆきます。チャネルリソースには限りがあることですし、基本チャネルもしくはその他の論理チャネルの中から一本だけを同時に利用できる仕様にしています。
ところでこの TelephonyManager API ですが、誰でも自由に使えるものではありません。久しぶりにテスト SIM の ARA-M を改変し、作成中のアプリに UICC Carrier Privileges を与えることにしました。
関連のある記事というか日記
普段の業務では使わない Kotlin を学ぶべく、のんびりコードを書いています。
前回の記事というか日記はこちら。
cheerio-the-bear.hatenablog.com
次の回はこちらです。
Mockito や Shadow を使ってテストコードを書くのは楽しい
Kotlin でコード書くのはとても楽しいですし、Mockito や Shadow を使ってテストコードを書くのも楽しいですね。業務で携わっているコードにはテストコードがあまり用意されていないものもありますので、このように自分で書いてみるのはとても有意義です。しばらく使わないとすぐに忘れてしまうでしょうから、今回のテストコードを書く際に使ったものたちについて簡単にメモしておこうと思います。
テストコードにパーミッションを与える
今回のテストの対象としたクラスでは、指定した SIM カードスロットに対応するアクティブな SubscriptionInfo を取得するために、SubscriptionManager の getActiveSubscriptionInfoForSimSlotIndex() を呼んでいます。API の実行には READ_PHONE_STATE パーミッションが必要になるので、テストコードには GrantPermissionRule を使って同パーミッションを与えました。
@Rule @JvmField val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.READ_PHONE_STATE)
Kotlin コード上で機能させるために、@JvmField アノテーションを付けています。
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() が一度ずつ呼び出されていたかどうかをチェックしています。