Background
In the past, I've found a special app called "Purchased apps" that somehow gets a list of the apps you've purchased. Not seeing any API for this, I asked how does it do it (and sadly still couldn't find a clear answer and a POC to demonstrate it).
The problem
Time passed, and I've noticed there is actually an open sourced app called "Aurora Store" (repository here) that can get about as much information as the Play Store. Screenshot from it:
Thing is, I got issues trying to figure out how to use its code properly, and the weird thing is that those apps get the information from different sources.
What I've tried
So, seeing it allows you to login to Google, and then get the "library" information (history of installed apps), I decided to give it a go (full sample on Github, here) :
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private val cookieManager = CookieManager.getInstance()
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val cachedEmail = defaultSharedPreferences.getString("email", null)
val cachedAasToken = defaultSharedPreferences.getString("aasToken", null)
if (cachedEmail != null && cachedAasToken != null) {
onGotAasToken(applicationContext, cachedEmail, cachedAasToken)
} else {
webView = findViewById(R.id.webView)
cookieManager.removeAllCookies(null)
cookieManager.acceptThirdPartyCookies(webView)
cookieManager.setAcceptThirdPartyCookies(webView, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
webView.settings.safeBrowsingEnabled = false
}
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
val cookies = CookieManager.getInstance().getCookie(url)
val cookieMap: MutableMap<String, String> = AC2DMUtil.parseCookieString(cookies)
val oauthToken: String? = cookieMap[AUTH_TOKEN]
oauthToken?.let {
webView.evaluateJavascript("(function() { return document.getElementById('profileIdentifier').innerHTML; })();") {
val email = it.replace("\"".toRegex(), "")
Log.d("AppLog", "got email?${email.isNotBlank()} got oauthToken?${oauthToken.isNotBlank()}")
buildAuthData(applicationContext, email, oauthToken)
}
} ?: Log.d("AppLog", "could not get oauthToken")
}
}
webView.settings.apply {
allowContentAccess = true
databaseEnabled = true
domStorageEnabled = true
javaScriptEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
}
webView.loadUrl(EMBEDDED_SETUP_URL)
}
}
companion object {
const val EMBEDDED_SETUP_URL =
"https://accounts.google.com/EmbeddedSetup/identifier?flowName=EmbeddedSetupAndroid"
const val AUTH_TOKEN = "oauth_token"
private fun buildAuthData(context: Context, email: String, oauthToken: String?) {
thread {
try {
val aC2DMResponse: Map<String, String> =
AC2DMTask().getAC2DMResponse(email, oauthToken)
val aasToken = aC2DMResponse["Token"]!!
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putString("email", email).putString("aasToken", aasToken).apply()
onGotAasToken(context, email, aasToken)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun onGotAasToken(context: Context, email: String, aasToken: String) {
thread {
val properties = NativeDeviceInfoProvider(context).getNativeDeviceProperties()
val authData = AuthHelper.build(email, aasToken, properties)
val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient())
var offset = 0
Log.d("AppLog", "list of purchase history:")
while (true) {
val purchaseHistory = purchaseHelper.getPurchaseHistory(offset)
if (purchaseHistory.isNullOrEmpty())
break
val size = purchaseHistory.size
offset += size
purchaseHistory.forEach {
Log.d("AppLog", "${it.packageName} ${it.displayName}")
}
}
Log.d("AppLog", "done")
}
}
}
}
It seems it got the token it needs (and the email), but sadly it seems to get 2 apps and that's it, and then when I try to get the next ones, I get the same 2 apps, twice more, meaning as such:
list of purchase history:
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
dev.southpaw.dungeon Dungeon Live Wallpaper
com.crydata.mylivewallpaper Hex AMOLED Neon Live Wallpaper 2021
and on the last time it tries to get the next chunk of apps, it crashes with this exception:
FATAL EXCEPTION: Thread-4
Process: com.lb.getplaystoreinstalledappshistory, PID: 6149
Server(code=400, reason=Bad Request)
at com.aurora.gplayapi.helpers.AppDetailsHelper.getAppByPackageName(AppDetailsHelper.kt:115)
at com.aurora.gplayapi.helpers.PurchaseHelper.getPurchaseHistory(PurchaseHelper.kt:63)
at com.lb.getplaystoreinstalledappshistory.MainActivity$Companion$onGotAasToken$1.invoke(MainActivity.kt:96)
at com.lb.getplaystoreinstalledappshistory.MainActivity$Companion$onGotAasToken$1.invoke(MainActivity.kt:68)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
The questions
- What's wrong with how I tried to get the list of apps? How can I get it right, ordered by time installed?
- Is there any way to get the time they were installed (or any clue about it) ? Somehow the "Purchased apps" app got the time. Granted it was only for purchased apps, but still...
- The "Purchased apps" app even got login better, as it doesn't require user-name and password. Instead it offers a dialog to choose the account. Assuming I get it right, is it possible to get the same information using the same login dialog ?