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),
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)
67 override fun hasStableIds(): Boolean = true
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list