Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import coredevices.pebble.rememberLibPebble
import coredevices.pebble.ui.TopBarIconButtonWithToolTip
import coredevices.pebble.ui.toPngBytes
import io.rebble.libpebblecommon.connection.ConnectedPebble
import io.rebble.libpebblecommon.util.toImageBitmap
import coredevices.ui.CoreLinearProgressIndicator
import coredevices.ui.PebbleElevatedButton
import coredevices.ui.SignInDialog
Expand Down Expand Up @@ -460,7 +461,7 @@ fun BugReportScreen(
screenshotLoading = true
scope.launch {
try {
watchScreenshot = connectedScreenshotWatch.takeScreenshot()
watchScreenshot = connectedScreenshotWatch.takeScreenshot()?.toImageBitmap()
} catch (e: Exception) {
Logger.withTag("BugReportScreen").e(e) { "Failed to capture screenshot" }
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import android.graphics.BitmapFactory
import android.os.Handler
import android.os.Looper
import android.provider.ContactsContract
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Logger
import io.rebble.libpebblecommon.connection.AppContext
import io.rebble.libpebblecommon.contacts.SystemContact
import io.rebble.libpebblecommon.contacts.SystemContacts
import io.rebble.libpebblecommon.di.LibPebbleCoroutineScope
import io.rebble.libpebblecommon.image.PebbleBitmap
import io.rebble.libpebblecommon.util.toPebbleBitmap
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -50,7 +50,7 @@ class AndroidSystemContacts(
return flow
}

override suspend fun getContactImage(lookupKey: String): ImageBitmap? = try {
override suspend fun getContactImage(lookupKey: String): PebbleBitmap? = try {
val contactId = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
arrayOf(ContactsContract.Contacts._ID),
Expand All @@ -70,7 +70,7 @@ class AndroidSystemContacts(

ContactsContract.Contacts.openContactPhotoInputStream(contentResolver,
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId))?.use { inputStream ->
return BitmapFactory.decodeStream(inputStream).asImageBitmap()
return BitmapFactory.decodeStream(inputStream).toPebbleBitmap()
}

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,36 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import io.rebble.libpebblecommon.connection.AppContext
import io.rebble.libpebblecommon.notification.DrawableConverter.convertDrawableToPainter
import io.rebble.libpebblecommon.image.PebbleBitmap
import io.rebble.libpebblecommon.util.toPebbleBitmap

actual fun iconFor(packageName: String, appContext: AppContext): ImageBitmap? {
actual fun iconFor(packageName: String, appContext: AppContext): PebbleBitmap? {
return try {
appContext.context.packageManager.getApplicationIcon(packageName).convertDrawableToPainter()
appContext.context.packageManager.getApplicationIcon(packageName).toBitmap().toPebbleBitmap()
} catch (e: Exception) {
null
}
}

object DrawableConverter {
fun Drawable.convertDrawableToPainter(): ImageBitmap {
return toBitmap().asImageBitmap()
private fun Drawable.toBitmap(): Bitmap {
if (this is BitmapDrawable && this.bitmap != null) {
return this.bitmap
}

private fun Drawable.toBitmap(): Bitmap {
if (this is BitmapDrawable && this.bitmap != null) {
return this.bitmap
}

val bitmap = if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
Bitmap.createBitmap(
1,
1,
Bitmap.Config.ARGB_8888
) // Single color bitmap will be created of 1x1 pixel
} else {
Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
}
val bitmap = if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
Bitmap.createBitmap(
1,
1,
Bitmap.Config.ARGB_8888
) // Single color bitmap will be created of 1x1 pixel
} else {
Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
}

val canvas = Canvas(bitmap)
setBounds(0, 0, canvas.width, canvas.height)
draw(canvas)
val canvas = Canvas(bitmap)
setBounds(0, 0, canvas.width, canvas.height)
draw(canvas)

return bitmap
}
}
return bitmap
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import android.graphics.Bitmap
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Logger
import io.rebble.libpebblecommon.image.PebbleBitmap

fun Bitmap.toPebbleBitmap(): PebbleBitmap {
val argb = if (config == Bitmap.Config.ARGB_8888) this else copy(Bitmap.Config.ARGB_8888, false)
val pixels = IntArray(argb.width * argb.height)
argb.getPixels(pixels, 0, argb.width, 0, 0, argb.width, argb.height)
return PebbleBitmap(width = argb.width, height = argb.height, pixels = pixels)
}

actual fun createImageBitmapFromPixelArray(
pixels: IntArray,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package io.rebble.libpebblecommon.connection

import io.rebble.libpebblecommon.connection.endpointmanager.FirmwareUpdater
import io.rebble.libpebblecommon.connection.endpointmanager.InstalledLanguagePack
import io.rebble.libpebblecommon.connection.endpointmanager.LanguagePackInstallState
import io.rebble.libpebblecommon.connection.endpointmanager.musiccontrol.MusicTrack
import io.rebble.libpebblecommon.image.PebbleBitmap
import io.rebble.libpebblecommon.js.PKJSApp
import io.rebble.libpebblecommon.metadata.WatchColor
import io.rebble.libpebblecommon.metadata.WatchHardwarePlatform
import io.rebble.libpebblecommon.music.MusicAction
import io.rebble.libpebblecommon.music.PlaybackState
import io.rebble.libpebblecommon.music.RepeatType
import io.rebble.libpebblecommon.packets.ProtocolCapsFlag
import io.rebble.libpebblecommon.protocolhelpers.PebblePacket
import io.rebble.libpebblecommon.services.FirmwareVersion
import io.rebble.libpebblecommon.services.WatchInfo
import io.rebble.libpebblecommon.services.appmessage.AppMessageData
import io.rebble.libpebblecommon.services.appmessage.AppMessageResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.io.files.Path
import kotlin.random.Random
import kotlin.time.Instant
import kotlin.uuid.Uuid

class FakeConnectedDevice(
override val identifier: PebbleIdentifier,
override val firmwareUpdateAvailable: FirmwareUpdateCheckState,
override val firmwareUpdateState: FirmwareUpdater.FirmwareUpdateStatus,
override val name: String,
override val nickname: String?,
override val color: WatchColor = run {
val white = Random.nextBoolean()
if (white) {
WatchColor.Pebble2DuoWhite
} else {
WatchColor.Pebble2DuoBlack
}
},
override val watchType: WatchHardwarePlatform = WatchHardwarePlatform.CORE_ASTERIX,
override val lastConnected: Instant = Instant.DISTANT_PAST,
override val serial: String = "XXXXXXXXXXXX",
override val runningFwVersion: String = "v1.2.3-core",
override val connectionFailureInfo: ConnectionFailureInfo?,
override val usingBtClassic: Boolean = false,
override val capabilities: Set<ProtocolCapsFlag> = emptySet()
) : ConnectedPebbleDevice {

override fun forget() {}
override fun setNickname(nickname: String?) {
}

override fun connect() {}

override fun disconnect() {}

override suspend fun sendPing(cookie: UInt): UInt = cookie

override fun resetIntoPrf() {}

override fun createCoreDump() {}
override fun factoryReset() {}

override suspend fun sendPPMessage(bytes: ByteArray) {}

override suspend fun sendPPMessage(ppMessage: PebblePacket) {}

override val inboundMessages: Flow<PebblePacket> = MutableSharedFlow()
override val rawInboundMessages: Flow<ByteArray> = MutableSharedFlow()

override fun sideloadFirmware(path: Path) {}

override fun updateFirmware(update: FirmwareUpdateCheckResult.FoundUpdate) {}

override fun checkforFirmwareUpdate() {}

override suspend fun launchApp(uuid: Uuid) {}

override suspend fun stopApp(uuid: Uuid) {}

override val runningApp: StateFlow<Uuid?> = MutableStateFlow(null)
override val watchInfo: WatchInfo = WatchInfo(
runningFwVersion = FirmwareVersion.from(
runningFwVersion,
isRecovery = false,
gitHash = "",
timestamp = kotlin.time.Instant.DISTANT_PAST,
isDualSlot = false,
isSlot0 = false,
)!!,
recoveryFwVersion = FirmwareVersion.from(
runningFwVersion,
isRecovery = true,
gitHash = "",
timestamp = kotlin.time.Instant.DISTANT_PAST,
isDualSlot = false,
isSlot0 = false,
)!!,
platform = watchType,
bootloaderTimestamp = kotlin.time.Instant.DISTANT_PAST,
board = "board",
serial = serial,
btAddress = "11:22:33:44:55:66",
resourceCrc = -9999999,
resourceTimestamp = kotlin.time.Instant.DISTANT_PAST,
language = "en-GB",
languageVersion = 1,
capabilities = emptySet(),
isUnfaithful = false,
healthInsightsVersion = null,
javascriptVersion = null,
color = color,
)

override suspend fun updateTime() {}
override suspend fun updateTimeIfNeeded() {}

override fun inboundAppMessages(appUuid: Uuid): Flow<AppMessageData> {
return MutableSharedFlow()
}

override val transactionSequence: Iterator<UByte> = iterator { }

override suspend fun sendAppMessage(appMessageData: AppMessageData): AppMessageResult =
AppMessageResult.ACK(appMessageData.transactionId)

override suspend fun sendAppMessageResult(appMessageResult: AppMessageResult) {}

override suspend fun gatherLogs(): Path? = null

override suspend fun getCoreDump(unread: Boolean): Path? = null

override suspend fun updateTrack(track: MusicTrack) {}

override suspend fun updatePlaybackState(
state: PlaybackState,
trackPosMs: UInt,
playbackRatePct: UInt,
shuffle: Boolean,
repeatType: RepeatType
) {
}

override suspend fun updatePlayerInfo(packageId: String, name: String) {}

override suspend fun updateVolumeInfo(volumePercent: UByte) {}

override val musicActions: Flow<MusicAction> = MutableSharedFlow()
override val updateRequestTrigger: Flow<Unit> = MutableSharedFlow()

@Deprecated("Use more generic currentCompanionAppSession instead and cast if necessary")
override val currentPKJSSession: StateFlow<PKJSApp?> = MutableStateFlow(null)
override val currentCompanionAppSessions: StateFlow<List<CompanionApp>> = MutableStateFlow(emptyList())

override suspend fun startDevConnection() {}
override suspend fun stopDevConnection() {}
override val devConnectionActive: StateFlow<Boolean> = MutableStateFlow(false)
override val batteryLevel: Int? = 50
override suspend fun takeScreenshot(): PebbleBitmap {
// Return an orange square as a placeholder
val width = 144
val height = 168
val pixels = IntArray(width * height) { 0xFFFA4A36.toInt() }
return PebbleBitmap(width, height, pixels)
}

override fun installLanguagePack(path: Path, name: String) {
}

override fun installLanguagePack(url: String, name: String) {
}

override val languagePackInstallState: LanguagePackInstallState =
LanguagePackInstallState.Idle()
override val installedLanguagePack: InstalledLanguagePack? = null

override suspend fun requestHealthData(fullSync: Boolean): Boolean = true
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.rebble.libpebblecommon.connection

import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.ImageBitmap
import androidx.paging.PagingSource
import co.touchlab.kermit.Logger
import io.rebble.libpebblecommon.ErrorTracker
import io.rebble.libpebblecommon.Housekeeping
Expand All @@ -17,11 +14,11 @@ import io.rebble.libpebblecommon.connection.bt.BluetoothStateProvider
import io.rebble.libpebblecommon.connection.bt.ble.transport.GattServerManager
import io.rebble.libpebblecommon.connection.endpointmanager.timeline.ActionOverrides
import io.rebble.libpebblecommon.connection.endpointmanager.timeline.CustomTimelineActionHandler
import io.rebble.libpebblecommon.contacts.Contacts
import io.rebble.libpebblecommon.contacts.PhoneContactsSyncer
import io.rebble.libpebblecommon.database.dao.AppWithCount
import io.rebble.libpebblecommon.database.dao.ChannelAndCount
import io.rebble.libpebblecommon.database.dao.HealthDao
import io.rebble.libpebblecommon.database.dao.ContactWithCount
import io.rebble.libpebblecommon.database.dao.TimelineNotificationRealDao
import io.rebble.libpebblecommon.database.dao.VibePatternDao
import io.rebble.libpebblecommon.database.dao.WatchPreference
Expand All @@ -41,6 +38,7 @@ import io.rebble.libpebblecommon.di.initKoin
import io.rebble.libpebblecommon.health.Health
import io.rebble.libpebblecommon.health.HealthDebugStats
import io.rebble.libpebblecommon.health.HealthSettings
import io.rebble.libpebblecommon.image.PebbleBitmap
import io.rebble.libpebblecommon.js.InjectedPKJSHttpInterceptors
import io.rebble.libpebblecommon.js.JsTokenUtil
import io.rebble.libpebblecommon.locker.AppBasicProperties
Expand Down Expand Up @@ -85,7 +83,6 @@ sealed class PebbleConnectionEvent {
data class PebbleDisconnectedEvent(val identifier: PebbleIdentifier) : PebbleConnectionEvent()
}

@Stable
interface LibPebble : Scanning, RequestSync, LockerApi, NotificationApps, CallManagement, Calendar,
OtherPebbleApps, PKJSToken, Watches, Errors, Contacts, AnalyticsEvents, HealthApi, WatchPrefs,
SystemGeolocation, Timeline, Vibrations, Weather, HealthDataApi {
Expand Down Expand Up @@ -305,14 +302,6 @@ interface LockerApi {
val activeWatchface: StateFlow<LockerWrapper?>
}

interface Contacts {
fun getContactsWithCounts(searchTerm: String, onlyNotified: Boolean): PagingSource<Int, ContactWithCount>
fun getContact(id: String): Flow<ContactWithCount?>
fun updateContactState(contactId: String, muteState: MuteState, vibePatternName: String?)
suspend fun getContactImage(lookupKey: String): ImageBitmap?
}

@Stable
interface NotificationApps {
fun notificationApps(): Flow<List<AppWithCount>>
fun notificationAppChannelCounts(packageName: String): Flow<List<ChannelAndCount>>
Expand Down Expand Up @@ -341,7 +330,7 @@ interface NotificationApps {
fun deleteNotificationRule(rule: NotificationRuleEntity)

/** Will only return a value on Android */
suspend fun getAppIcon(packageName: String): ImageBitmap?
suspend fun getAppIcon(packageName: String): PebbleBitmap?
}

interface Vibrations {
Expand All @@ -365,7 +354,7 @@ interface PKJSToken {

// Impl

class LibPebble3(
class LibPebble3 internal constructor(
private val watchManager: WatchManager,
private val scanning: Scanning,
private val locker: Locker,
Expand Down
Loading