Tag Archives: Android

记一个Nexus 6p解锁loop问题

用了两年多的Nexus 6p出现问题了。
表现为:启动到主屏的时候,输入正确的密码后,系统闪退,然后再次要求输入密码。
在Android Central里,有人报过类似的问题

adb log显示:

01-11 19:26:41.950 4549 5940 W System.err: java.security.UnrecoverableKeyException: Failed to obtain information about key
01-11 19:26:41.950 4549 5940 W System.err: at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(AndroidKeyStoreProvider.java:282)
01-11 19:26:41.950 4549 5940 W System.err: at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:98)
01-11 19:26:41.950 4549 5940 W System.err: at java.security.KeyStore.getKey(KeyStore.java:1062)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.SyntheticPasswordCrypto.decryptBlob(SyntheticPasswordCrypto.java:120)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.SyntheticPasswordManager.decryptSPBlob(SyntheticPasswordManager.java:1040)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.SyntheticPasswordManager.unwrapSyntheticPasswordBlob(SyntheticPasswordManager.java:906)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.SyntheticPasswordManager.unwrapPasswordBasedSyntheticPassword(SyntheticPasswordManager.java:843)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.LockSettingsService.spBasedDoVerifyCredential(LockSettingsService.java:2105)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.LockSettingsService.doVerifyCredential(LockSettingsService.java:1553)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.server.locksettings.LockSettingsService.checkCredential(LockSettingsService.java:1526)
01-11 19:26:41.950 4549 5940 W System.err: at com.android.internal.widget.ILockSettings$Stub.onTransact(ILockSettings.java:164)
01-11 19:26:41.950 4549 5940 W System.err: at android.os.Binder.execTransact(Binder.java:697)
01-11 19:26:41.950 4549 5940 W System.err: Caused by: android.security.KeyStoreException: Key not yet valid
01-11 19:26:41.950 4549 5940 W System.err: at android.security.KeyStore.getKeyStoreException(KeyStore.java:697)
01-11 19:26:41.950 4549 5940 W System.err: at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(AndroidKeyStoreProvider.java:283)
01-11 19:26:41.950 4549 5940 W System.err: ... 11 more
01-11 19:26:41.951 4549 5940 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
01-11 19:26:41.951 4549 5940 E JavaBinder: java.lang.RuntimeException: Failed to decrypt blob
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.SyntheticPasswordCrypto.decryptBlob(SyntheticPasswordCrypto.java:129)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.SyntheticPasswordManager.decryptSPBlob(SyntheticPasswordManager.java:1040)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.SyntheticPasswordManager.unwrapSyntheticPasswordBlob(SyntheticPasswordManager.java:906)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.SyntheticPasswordManager.unwrapPasswordBasedSyntheticPassword(SyntheticPasswordManager.java:843)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.LockSettingsService.spBasedDoVerifyCredential(LockSettingsService.java:2105)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.LockSettingsService.doVerifyCredential(LockSettingsService.java:1553)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.LockSettingsService.checkCredential(LockSettingsService.java:1526)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.internal.widget.ILockSettings$Stub.onTransact(ILockSettings.java:164)
01-11 19:26:41.951 4549 5940 E JavaBinder: at android.os.Binder.execTransact(Binder.java:697)
01-11 19:26:41.951 4549 5940 E JavaBinder: Caused by: java.security.UnrecoverableKeyException: Failed to obtain information about key
01-11 19:26:41.951 4549 5940 E JavaBinder: at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(AndroidKeyStoreProvider.java:282)
01-11 19:26:41.951 4549 5940 E JavaBinder: at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:98)
01-11 19:26:41.951 4549 5940 E JavaBinder: at java.security.KeyStore.getKey(KeyStore.java:1062)
01-11 19:26:41.951 4549 5940 E JavaBinder: at com.android.server.locksettings.SyntheticPasswordCrypto.decryptBlob(SyntheticPasswordCrypto.java:120)
01-11 19:26:41.951 4549 5940 E JavaBinder: ... 8 more
01-11 19:26:41.951 4549 5940 E JavaBinder: Caused by: android.security.KeyStoreException: Key not yet valid
01-11 19:26:41.951 4549 5940 E JavaBinder: at android.security.KeyStore.getKeyStoreException(KeyStore.java:697)
01-11 19:26:41.951 4549 5940 E JavaBinder: at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(AndroidKeyStoreProvider.java:283)
01-11 19:26:41.951 4549 5940 E JavaBinder: ... 11 more

根据crash的log看,应该是系统在从keystore里读key的时候,认为时间无效:

01-11 19:26:41.950 4549 5940 W System.err: Caused by: android.security.KeyStoreException: Key not yet valid

相关的code,大概是在https://android.googlesource.com/platform/system/security/+/master/keystore/keystore_keymaster_enforcement.h#41

bool activation_date_valid(uint64_t activation_date) const override {
  time_t now = time(NULL);
  if (now == static_cast<time_t>(-1)) {
    // Failed to obtain current time -- fail safe: activation_date hasn't yet occurred.
    return false;
  } else if (now < 0) {
    // Current time is prior to start of the epoch -- activation_date hasn't yet occurred.
    return false;
  }
  // time(NULL) returns seconds since epoch and "loses" milliseconds information. We thus add
  // 999 ms to now_date to avoid a situation where an activation_date of up to 999ms in the
  // past may still be considered to still be in the future. This can be removed once
  // time(NULL) is replaced by a millisecond-precise source of time.
  uint64_t now_date = static_cast<uint64_t>(now) * 1000 + 999;
  return now_date >= activation_date;
}

因为有root,可以尝试设置系统时间,结果还是没用,即使设置到2250年,它还是报同样的错。所以我也不知道到底问题是不是在这里了。

然后注意到hwclock不太对:

angler:/sys/class/rtc/rtc0 # date "030100002018"
Thu Mar 1 00:00:00 CST 2018
angler:/sys/class/rtc/rtc0 # hwclock -l
Mon Jan 12 09:21:32 1970 0.000000 seconds
angler:/sys/class/rtc/rtc0 # hwclock -w
hwclock: ioctl 4024700a: Invalid argument

高通的这个RTC莫非根本不能写,只能读?

angler:/sys/class/rtc/rtc0 # ls -l
total 0
-r--r--r-- 1 root root 4096 2018-03-01 00:01 date
-r--r--r-- 1 root root 4096 2018-03-01 00:01 dev
lrwxrwxrwx 1 root root 0 2018-03-01 00:01 device -> ../../../qpnp-rtc-8
-r--r--r-- 1 root root 4096 2018-03-01 00:01 hctosys
-rw-r--r-- 1 root root 4096 2018-03-01 00:01 max_user_freq
-r--r--r-- 1 root root 4096 2018-03-01 00:01 name
drwxr-xr-x 2 root root 0 2018-03-01 00:01 power
-r--r--r-- 1 root root 4096 2018-03-01 00:01 since_epoch
lrwxrwxrwx 1 root root 0 2018-03-01 00:01 subsystem -> ../../../../../class/rtc
-r--r--r-- 1 root root 4096 2018-03-01 00:01 time
-rw-r--r-- 1 root root 4096 2018-03-01 00:01 uevent

最后我怀疑是time(NULL);返回了-1,但是没有log,也不知道是不是这样。

总之,这个问题,好像只能factory reset了。
如果再出问题,能只编个AOSP,加上log看一下了。

Share

在Android 7.0上解决感叹号问题 [更新至7.1.1]

背景

从Android 5.0 (Lollipop)开始,安卓为了检测各种需要登录的Wifi服务,提供了captive_portal_detection_enabledcaptive_portal_server这两个设置,分别作为检测的开关和服务器。原理是访问http://<server>/generate_204这个URL:

  • 如果返回204,就说明wifi可以直接连接,不需要登录;
  • 如果返回非204并且非空的内容,说明wifi需要登录。

默认的服务器是被墙的google的服务器,所以总是检测不正确,导致出来感叹号。

具体情况可以参看小狐狸的这篇文章,里面详细介绍了原理,以及如何设置墙内的服务器。

问题

不过,从Android 7.0 (Nougat)开始,这个设置稍微有点变化,在某些服务器上会导致又出现感叹号,这里介绍一下。

Android 7.0 引入了一个新的设置:CAPTIVE_PORTAL_USE_HTTPS(captive_portal_use_https),默认值是1,也就是说,默认情况,它会用https://<server>/generate_204这个URL来判断isCaptivePortal()
具体的code请移步NetworkMonitor.java#285

假如原来的服务器不支持HTTPS,或者支持了HTTPS但是这个URL没有返回204,都会导致感叹号的问题。

解决

所以解决问题的方式也很简单:

  • 要么配置墙内的服务器,让它在https的情况也也返回204;
  • 要么在手机上把HTTPS的值设为0 (settings put global captive_portal_use_https 0)

比如说,V2EX刚提供了captive.v2ex.co这个URL,同时支持HTTP和HTTPS。
本站也提供http://ping.mine260309.me/generate_204https://mine260309.me/generate_204这两个URL。

更新

升级到7.1.1之后,会发现感叹号(或者x号)又出现了。
感谢@tedjiang的comment,解决办法是:

settings put global captive_portal_https_url https://captive.v2ex.co/generate_204
Share

Android M – Systemless Unroot, OTA and re-Root

Since Google is now pushing monthly security updates, it becomes a regular procedure for me to temporarily un-root my phone to get OTA updates.
This post introduces the steps of how to unroot Android M (6.0 or above), get OTA update and then re-root it.

[Update] Let’s take Nexus6p as example to OTA from angler-MHC19I to angler-MHC19Q.

Pre-condition

Preparation

  • Factory image of MHC19I
  • TWRP (e.g. 3.0.0.1)
  • SuperSU (2.60 or above, e.g. 2.68)

Steps

Unroot

  1. Extract MHC19I’s factory image to get boot.img and recovery.img
    tar -xf angler-mhc19i-factory-8c31db3f.tgz angler-mhc19i/image-angler-mhc19i.zip
    cd angler-mhc19i
    unzip image-angler-mhc19i.zip boot.img recovery.img
    
  2. Flash stock boot and recovery
    adb reboot bootloader
    fastboot flash boot boot.img
    fastboot flash recovery recovery.img
    

Now the phone is un-rooted and we get the stock Android.

OTA or Sideload

If you got OTA update from Google, just run OTA, Android does the OTA automatically.

If you did not get OTA update, you have the option to use adb sideload to manually flash the OTA package.

  1. Get the OTA package;
  2. Go to stock recovery, in recovery screen, press and hold the Power key and the Volume up key for a few seconds, and then let go of the Volume up key, but keep pressing Power.
  3. In stock recovery, run below command to do the OTA upgrade:
    adb sideload f7303e0c33450419deeb292b0887c1595fb5588d.signed-angler-MHC19Q-from-MHC19I.zip
    

After OTA, we’re now on MHC19Q (April 2016 release).

Root again

  1. Flash TWRP
    adb reboot bootloader
    fastboot flash recovery twrp-3.0.0-1-angler.img
    
  2. Flash SuperSU
    • Press the Volumn and Power key to enter Recovery
    • In TWRP, flash BETA-SuperSU-v2.68-20160228150503.zip
  3. Wipe cache/dalvik
  4. Reboot to system.

Now the phone is rooted again systemlessly.
Enjoy the new version 😉

Share

Handle Discontinuity and PAT/PMT change in Android Multicast Playback

In previous post it briefly introduces how to implement multicast playback in Android.
In real world, multicast may get discontinuities or PAT/PMT changes, which will cause the playback intermittent or failure if it’s not correctly handled.

In this post, it will introduces how to handle discontinuity and PAT/PMT changes.

Background

In NuPlayer::Decoder::fetchInputData(), it handles three types of DISCONTINUITY:

  • DISCONTINUITY_TIME
  • DISCONTINUITY_AUDIO_FORMAT
  • DISCONTINUITY_VIDEO_FORMAT

Originally, they are used to handle adaptive cases in HLS, e.g. switch from low-resolution stream to high-resolution stream. They’re perfect for multicast stream’s discontinuity and PAT/PMT change as well.

Handle Discontinuity

In ATSParser::Stream::parse(), it already checks the ContinuityCounter in TS streams. We can just queue a time discontinuity unit, then NuPlayer::Decoder will handle the case, reset the playback and re-sync A/V.

if (mExpectedContinuityCounter >= 0
    && (unsigned)mExpectedContinuityCounter != continuity_counter) {
  ALOGI("discontinuity on stream pid 0x%04x", mElementaryPID);
  ...
  signalDiscontinuity(DISCONTINUITY_TIME, NULL);
  ...
}

In ATSParser::Program::convertPTSToTimestamp(), there is a special flag for calculating time stamp from PTS.

if (!(mParser->mFlags & TS_TIMESTAMPS_ARE_ABSOLUTE)) {
  if (!mFirstPTSValid) {
    mFirstPTSValid = true;
    mFirstPTS = PTS;
    PTS = 0;
  } else if (PTS < mFirstPTS) {
    PTS = 0;
  } else {
    PTS -= mFirstPTS;
  }
}
...

If TS_TIMESTAMPS_ARE_ABSOLUTE is not set, it uses the first valid PTS as the base time stamp, and assumes the PTS is increasing all the time. This is not the case for multicast live streaming. So we need to set this flag to let it calculate time stamp by absolute value of PTS.

  // In MulticastSession::onConnect()
  mTSParser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);

With the changes, multicast discontinuity is correctly handled and it works fine when multicast stream is looped.

Handle PAT/PMT change

In ATSParser implementation, it just parses PAT/PMT and store the program table and the A/V streams. If PAT/PMT is changed, it just update the program table and streams, and requires the “user” of ATSParser to handle the change, which is quite complicated.
In the other hand, when PAT/PMT is changed in multicast, we know the stream is changed and thus can just throw away the previous program table. So why not delete the previous ATSParser and create a new one to handle the new stream? It makes things much easier.
The changes:

  • ATSParser
    1. Add an observer that should receive the event of PAT/PMT change
    2. Save PAT/PMT crc32 and detect the change; If it’s changed, call onPatPmtChange() and notify the observer
  • MulticastSession
    1. Receive parser’s event and call onPatPmtChanged() for PAT/PMT changed event
    2. Reset ATSParser by delete and create new one
    3. Queue FORMAT change DISCONTINUITY for both audio and video
    4. In dequeueAccessUnit(), check the discontinuity queue and return DISCONTINUITY for audio/video stream.

With the changes, ATSParser is reset when PAT/PMT changes, as if it plays a new stream, which is exactly the expected behavior.

Share

Multicast playback in Android

This article is to briefly introduce how to add multicast playback support in Android platform based on AOSP 5.0.0_r2 tag.

Background

  • NuPlayer is the default media player in Android L. It’s part of libmediaplayerservice located in frameworks/av/media/libmediaplayerservice
  • libstagefright is a library used by NuPlayer to provide a lot of media related functions, e.g. media download, parse and extract. Located in frameworks/av/media/libstagefright
  • chromium_org is a library used by Browser to provide all webview related functions. <video> tag in Webview also uses this library to render its content. Located in external/chromium_org
  • Gallery2 is a default system app that handles media playback intents. Located in packages/apps/Gallery

The above components will be changed to add multicast support.

Implementations

Precondition

Multicast as a kernel option, shall be enabled in kernel build.
Note: this option is enabled by default on Nexus Tablets, but disabled on Nexus Phones.
So you probably need to compile your custom kernel if you plan to test multicast on your phone.

  CONFIG_IP_MULTICAST=y
NuPlayer
  • MediaPlayerFactory::IFactory::scoreFactory() checks the URI scheme to decide which player shall be used to play a certain media.
    In NuPlayerFactory::scoreFactory(), multicast’s score shall be added to be chosen for playing multicast stream.
    if (!strncasecmp("http://", url, 7)
        || !strncasecmp("https://", url, 8)
        || !strncasecmp("file://", url, 7)) {
      … // return kOurScore
    }
    if (!strncasecmp("rtsp://", url, 7)) {
      return kOurScore;
    }
    if (!strncasecmp("udp://", url, 6)
        || !strncasecmp("igmp://", url, 7)) {
      return kOurScore;
    }
  • NuPlayer checks the URI scheme and creates different DataSource to receive media data. MulticastSource is added to handle multicast stream.
    In NuPlayer::setDataSourceAsync(), multicast shall be supported as below
	if (IsHTTPLiveURL(url)) {
	  … // new HTTPLiveSource
	} else if (!strncasecmp(url, "rtsp://", 7)) {
	  … // new RTSPSource
	}
	…
	else if (!strncasecmp(url, "udp://", 6)
	         || !strncasecmp("igmp://", url, 7)) {
	  source = new MulticastSource(notify, httpService, url, headers);
	}
	else {
	  … // Generic source
	}
  • MulticastSource inherits NuPlayer::Source and provide related functions. Basically it just handles events and pass all functions to MulticastSession, which is implemented in libstagefright.
libstagefright
  • Multicast socket related code shall be implemented to provide the socket interface.
  • MulticastSession uses the socket to receive media data, uses existing ATSParser to parse the TS stream, and extract so-called accessUnit to feed the decoder. It could be put at frameworks/av/media/libstagefright/multicast
    This class handles the main part of multicast playback.
  • MulticastDataSource is created by DataSource and used by MediaExtractor to extract the meta data of the media, e.g. whether it has video or audio, what the video resolution is, etc.
    This class is used in <video> tag playback, that webview will retrieve the media element’s metadata.
    Note: DataSource expects the media as static content, so it uses readAt(offset) to read the data from the source. But multicast is a stream, so MulticastDataSource shall be implemented carefully to provide the correct data with offset to DataSource.
  • DataSource shall be updated to create MulticastDataSource for multicast scheme.
	if (!strncasecmp("file://", uri, 7)) {
	  … // new FileSource
	} else if (!strncasecmp("http://", uri, 7)
	           || !strncasecmp("https://", uri, 8)
	           || isWidevine) {
	  … // new Http or Cache source
	} else if (!strncasecmp("data:", uri, 5)) {
	  … // new DataURISource
	} else if (!strncasecmp("igmp://", uri, 7)
	           || !strncasecmp("udp://", uri, 6)) {
	  source = new MulticastDataSource(uri);
	} else {
	  … // new FileSource
	}
chromium_org

By default, WebMediaPlayerAndroid does not support multicast scheme, and thus it treats the URI as a file and fails to load the media. WebMediaPlayerAndroid::load() shall be updated to support multicast scheme.

  if (url_.scheme() == "udp" || url_.scheme() == "igmp") {
    DVLOG(1) << "Do not load multicast";
    UpdateReadyState(WebMediaPlayer::ReadyStateHaveNothing);
    DidLoadMediaInfo(MediaInfoLoader::kOk, GURL(url), GURL(""), false);
    return;
  }
Gallery2

By default, Gallery2 does not handle multicast, and thus if an application sends a VIEW intent with multicast scheme, no application handles the intent and exception throws. AndroidManifest.xml shall be updated to support multicast scheme, so that it handles such VIEW intent.

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="udp" />
                <data android:scheme="igmp" />
                <data android:mimeType="video/*" />
                <data android:mimeType="audio/*" />
             </intent-filter>

Conclusion

The above is a simplified introduction of the changes needed to support multicast playback in AOSP.
Google will probably not officially add such support, but some fork may be interested in multicast. This could be a start of the work.

Share