git.iko.soy/opencode-android
on 2794fda help sign in
68 lines 3.3 kB history raw
1package soy.iko.opencode.platform
3import android.content.Context
4import android.content.Intent
5import android.widget.RemoteViews
6import android.widget.RemoteViewsService
7import soy.iko.opencode.MainActivity
8import soy.iko.opencode.R
9import soy.iko.opencode.data.repo.RecentSession
10import soy.iko.opencode.data.repo.RecentSessionsStore
11import java.security.MessageDigest
13/** Serves the list rows for [SessionsWidgetProvider] from [RecentSessionsStore]. */
14class SessionsWidgetService : RemoteViewsService() {
15 override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
16 SessionsWidgetFactory(applicationContext)
19private class SessionsWidgetFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
21 private var items: List<RecentSession> = emptyList()
23 override fun onCreate() { items = RecentSessionsStore.read(context) }
25 // Re-read from disk whenever the app signals the data changed (notifyAppWidgetViewDataChanged).
26 override fun onDataSetChanged() { items = RecentSessionsStore.read(context) }
28 override fun onDestroy() { items = emptyList() }
30 override fun getCount(): Int = items.size
32 override fun getViewAt(position: Int): RemoteViews {
33 val row = RemoteViews(context.packageName, R.layout.widget_session_item)
34 val session = items.getOrNull(position) ?: return row
35 row.setTextViewText(R.id.widget_item_title, session.title.ifBlank { context.getString(R.string.session) })
36 // Fill-in intent merged into the list's template: carries the session id the app opens.
37 row.setOnClickFillInIntent(
38 R.id.widget_item_root,
39 Intent().putExtra(MainActivity.EXTRA_SESSION_ID, session.id),
40 )
41 return row
42 }
44 override fun getLoadingView(): RemoteViews? = null
45 override fun getViewTypeCount(): Int = 1
46 // Derive a stable id from the session id via a collision-resistant SHA-256 digest
47 // (truncated to 63 bits) rather than String.hashCode(), whose 32-bit output can
48 // collide for distinct session ids — with hasStableIds=true the RemoteViews framework
49 // would then mis-recycle two sessions' row content. Mirrors SessionNotifications.notifId.
50 // Falls back to the position for an out-of-range index.
51 override fun getItemId(position: Int): Long {
52 val id = items.getOrNull(position)?.id ?: return position.toLong()
53 val digest = runCatching { MessageDigest.getInstance("SHA-256").digest(id.toByteArray()) }
54 .getOrNull() ?: return position.toLong()
55 // Take the first 8 bytes, mask to 63 bits (always positive) to fit a Long id.
56 // The shl/or/and infix precedence requires parenthesizing each shift individually
57 // (see SessionNotifications.notifId for the rationale).
58 return (((digest[0].toLong() and 0xFF) shl 56) or
59 ((digest[1].toLong() and 0xFF) shl 48) or
60 ((digest[2].toLong() and 0xFF) shl 40) or
61 ((digest[3].toLong() and 0xFF) shl 32) or
62 ((digest[4].toLong() and 0xFF) shl 24) or
63 ((digest[5].toLong() and 0xFF) shl 16) or
64 ((digest[6].toLong() and 0xFF) shl 8) or
65 (digest[7].toLong() and 0xFF)).and(0x7FFFFFFFFFFFFFFF)
66 }
67 override fun hasStableIds(): Boolean = true