Android shortcuts automatic deletion - android

I have a application where I can press and hold and then create a shortcut of that particular deck/folder but when I delete the folder from the app itself, the icon(shortcut) stays there leading to a crash when taped upon. I placed a check for NPE so now its not crashing but I want to know if we can delete the shortcut on the folder deletion itself. Is there a way in Android Studio(kotlin). Below if the code to create that shortcut
fun createIcon(context: Context, did: DeckId) {
// This code should not be reachable with lower versions
val shortcut = ShortcutInfoCompat.Builder(this, did.toString())
.setIntent(
Intent(context, Reviewer::class.java)
.setAction(Intent.ACTION_VIEW)
.putExtra("deckId", did)
)
.setIcon(IconCompat.createWithResource(context, R.mipmap.ic_launcher))
.setShortLabel(Decks.basename(col.decks.name(did)))
.setLongLabel(col.decks.name(did))
.build()
try {
val success = ShortcutManagerCompat.requestPinShortcut(this, shortcut, null)
// User report: "success" is true even if Vivo does not have permission
if (AdaptionUtil.isVivo) {
showThemedToast(this, getString(R.string.create_shortcut_error_vivo), false)
}
if (!success) {
showThemedToast(this, getString(R.string.create_shortcut_failed), false)
}
} catch (e: Exception) {
Timber.w(e)
showThemedToast(this, getString(R.string.create_shortcut_error, e.localizedMessage), false)
}
}

/** Disables the shortcut of the deck whose id is [did] */
fun disableDeckShortcut(did: DeckId) {
val childDids = col.decks.childDids(did, col.decks.childMap()).map { it.toString() }
val deckTreeDids = listOf(did.toString(), *childDids.toTypedArray())
val errorMessage: CharSequence = getString(R.string.deck_shortcut_doesnt_exist)
ShortcutManagerCompat.disableShortcuts(this, deckTreeDids, errorMessage)
}
Above is how I solved the issue if anyone wants to read the same in detail here is the link

Related

How to use firebase to update your Android App

I've seen that there are ways to update an app with Firebase Remote Config. Some sort of "Force Update" Notification. If anyone can explain it to me, that would be great.
How to use Firebase to update your Android App?
There are multiple ways in which you can update an Android app. The first one would be to store data in a database. Firebase has two real-time databases, Cloud Firestore and the Realtime Database. You can one or the other, according to the use case of your app. For that I recommend you check the following resource:
https://firebase.google.com/docs/database/rtdb-vs-firestore
When it comes to Remote Config, please notice that nowadays you can propagate Remote Config updates in real-time. That being said, there is no need to force anything. So I highly recommend that a look at that.
For Force update in a simple case the idea is
with firebase remort config sends the version number which you want for your application to be forced
then compare remort version with the local application version
if there is a mismatch then show a permanent dialog (cancelable=false) with a button when the user clicks on that button to open the application in the play store .
Check out this Small Class created for force update with remort config
class ForceUpdateChecker(private val context: Context, private val onUpdateNeededListener: OnUpdateNeededListener?) {
interface OnUpdateNeededListener {
fun onUpdateNeeded(updateUrl: String?)
}
fun check() {
val remoteConfig = FirebaseRemoteConfig.getInstance()
if (remoteConfig.getBoolean(KEY_UPDATE_REQUIRED)) {
val currentVersion = remoteConfig.getString(KEY_CURRENT_VERSION)
val appVersion = getAppVersion(context)
val updateUrl = remoteConfig.getString(KEY_UPDATE_URL)
if (!TextUtils.equals(currentVersion, appVersion)
&& onUpdateNeededListener != null
) {
onUpdateNeededListener.onUpdateNeeded(updateUrl)
}
}
}
private fun getAppVersion(context: Context): String {
var result = ""
try {
result = context.packageManager
.getPackageInfo(context.packageName, 0).versionName
result = result.replace("[a-zA-Z]|-".toRegex(), "")
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, e.message!!)
}
return result
}
class Builder(private val context: Context) {
private var onUpdateNeededListener: OnUpdateNeededListener? = null
fun onUpdateNeeded(onUpdateNeededListener: OnUpdateNeededListener?): Builder {
this.onUpdateNeededListener = onUpdateNeededListener
return this
}
fun build(): ForceUpdateChecker {
return ForceUpdateChecker(context, onUpdateNeededListener)
}
fun check(): ForceUpdateChecker {
val forceUpdateChecker = build()
forceUpdateChecker.check()
return forceUpdateChecker
}
}
companion object {
private val TAG = ForceUpdateChecker::class.java.simpleName
const val KEY_UPDATE_REQUIRED = "force_update_required"
const val KEY_CURRENT_VERSION = "force_update_current_version"
const val KEY_UPDATE_URL = "force_update_store_url"
fun with(context: Context): Builder {
return Builder(context)
}
}}
Call this like this in baseActivity (or from your landing page just not in splash screen)
ForceUpdateChecker.with(this).onUpdateNeeded(this).check();
In application on create add this
val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
// set in-app defaults
val remoteConfigDefaults: MutableMap<String, Any> = HashMap()
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_REQUIRED] = false
remoteConfigDefaults[ForceUpdateChecker.KEY_CURRENT_VERSION] = "1.0"
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_URL] =
"https://play.google.com/store/apps/details?id=com.com.classified.pems"
firebaseRemoteConfig.setDefaultsAsync(remoteConfigDefaults)
firebaseRemoteConfig.fetch(60) // fetch every minutes
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "remote config is fetched.")
firebaseRemoteConfig.fetchAndActivate()
}
}

How to use Zebra EMDK in release build?

So I Have a Zebra MC330M device.
I created earliar an application, and I would like to use the PDA built in barcode scanner.
If is run my app in debug mode, everything working well, I can read the barcodes, but If I create an staged or relase version apk, the barcode reader inactive, so the red light doesn't light if I press the button.
I created a simple,to demonstrate the problem, but you can reproduce the problem if you device has EMDK, else you get an exception.
Project
I implemented all of stuff by this tutorial: https://techdocs.zebra.com/emdk-for-android/11-0/tutorial/tutBasicScanningAPI
So I added this into gradle: compileOnly 'com.symbol:emdk:7.6.+'
I added thease two lines to the manfest:
<uses-permission android:name="com.symbol.emdk.permission.EMDK" />
<uses-library android:name="com.symbol.emdk" android:required="false"/>
I tried to add the dependency with implementation instead of compileOnly , but after that I got an Exception in this line: EMDKManager.getEMDKManager(context,object : EMDKManager.EMDKListener...
[UPDATE] When I push the scanner button, I get this error:
E/ActivityManager: Sending non-protected broadcast com.symbol.button.R1 from system 1208:system/1000 pkg android
java.lang.Throwable
at com.android.server.am.ActivityManagerService.checkBroadcastFromSystem(ActivityManagerService.java:18150)
at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:18728)
at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:18819)
at android.app.ContextImpl.sendBroadcastAsUser(ContextImpl.java:1040)
at com.android.server.RemoteKeyEventService.broadcastPublicKeyIntent(RemoteKeyEventService.java:672)
at com.android.server.RemoteKeyEventService.handleKeyEvent(RemoteKeyEventService.java:526)
at com.android.server.wm.InputMonitor.interceptKeyBeforeQueueing(InputMonitor.java:464)
at com.android.server.input.InputManagerService.interceptKeyBeforeQueueing(InputManagerService.java:1874)
My app is targeting API Level 30, and I put the permission inside the manifest.
I also tried to log the error, but there is no error, it seems the two listeners (status, and data) not triggered, or can not enable the scanner, but in this case I should get an error message.
Barcode Reader Class:
class ZebraBarcodeReader(
private val context: Context
){
private var emdkManager: EMDKManager? = null
private var barcodeManager: BarcodeManager? = null
private val barcodeLiveData = MutableLiveData<EventWrapper<String?>>()
init {
val result = EMDKManager.getEMDKManager(context,object : EMDKManager.EMDKListener{
override fun onOpened(p0: EMDKManager?) {
opened(p0)
}
override fun onClosed() {
closed()
}
})
if(result.statusCode != EMDKResults.STATUS_CODE.SUCCESS) {
Timber.d("$TIMBER_TAG ZebraBarcodeReader failed to connect")
}else{
Timber.d("$TIMBER_TAG EMDKManager object initialization in progress...")
}
}
fun getData(): LiveData<EventWrapper<String?>> = barcodeLiveData
private fun opened(manager: EMDKManager?){
emdkManager = manager
initBarcodeManager()
initScanner()
}
private var scanner: Scanner? = null
private fun initScanner() {
scanner = barcodeManager?.getDevice(BarcodeManager.DeviceIdentifier.DEFAULT)
if(scanner == null){
Timber.d("$TIMBER_TAG Scanner not found")
}else{
scanner?.addDataListener {scanDataCollection->
if(scanDataCollection != null && (scanDataCollection.result) == ScannerResults.SUCCESS){
val scanData = scanDataCollection.scanData
scanData.forEach {
val barcode = it.data
barcodeLiveData.postValue(EventWrapper(barcode))
}
}
}
scanner?.addStatusListener { statusData->
val state = statusData.state
var statusString = ""
when(state){
StatusData.ScannerStates.IDLE-> {
statusString = statusData.friendlyName + "is enabled and IDLE"
setConfig()
try {
scanner?.read()
}catch (e: ScannerException){
Timber.d(e)
}
}
StatusData.ScannerStates.WAITING-> {
statusString = "Scanner is waiting for trigger press"
}
StatusData.ScannerStates.SCANNING-> {
statusString = "Scanning"
}
StatusData.ScannerStates.DISABLED-> {
statusString = "Scanner is disabled"
}
StatusData.ScannerStates.ERROR-> {
statusString="An error occured"
}
else -> {
statusString="Another thing in else branch"
}
}
Timber.d("$TIMBER_TAG $statusString")
}
try {
scanner?.enable()
} catch (e: ScannerException) {
Timber.d("$TIMBER_TAG Scanner can not be enabled")
}
}
}
private fun setConfig() {
scanner?.let { s->
try {
val config = s.config
if(config.isParamSupported("config.scanParams.decodeHapticFeedback"))
config.scanParams.decodeHapticFeedback = true
s.config = config
}catch (e: Exception){
Timber.d(e)
}
}
}
private fun initBarcodeManager() {
barcodeManager = emdkManager?.getInstance(EMDKManager.FEATURE_TYPE.BARCODE) as BarcodeManager?
if(barcodeManager == null){
Timber.d("$TIMBER_TAG Barcode scanning not supported")
}
}
private fun closed(){
emdkManager?.let { m->
m.release()
emdkManager = null
}
Timber.d("$TIMBER_TAG : closed unexpectedly!")
}
fun destroy(){
emdkManager?.let { m->
m.release()
emdkManager = null
}
}
Please add the following to your manifest under the uses-permission tag:
<queries>
<package android:name="com.symbol.emdk.emdkservice" />
</queries>
Little bit weird for me, but I solved this problem.
I don't know why, but firstly I installed a debug version of my apk, after that I deleted it, and then I installed the staged/release version. I didn't work.
After that when I deleted the debug version, I restarted the device, and then installed release version, and it is works. I don't no why it was necessary, but I'm happy.
As an update, with A11, if you are using the EMDK to obtain the device's serial number, mac address, etc., then you should use:
<queries>
<package android:name="com.zebra.zebracontentprovider" />
<package android:name="com.symbol.dataanalytics" />
<package android:name="com.symbol.emdk.emdkservice" />
</queries>

make a custom call using TelecomManager.placeCall

I'm trying to make a custom implementation of a call using TelecomManager between two users who had installed my app on their devices
Following this guide I implemented connection service, subclass of Connection, added permissions, registered a PhoneAccount and so on...
The thing I'm struggling to understand for a third week already how to place a call between users of my app without using telephone number but user name or userId.
Below code starting to make a call from my device but this call never reaches end user device
telecomManager.placeCall(Uri.fromParts(/*tried also with PhoneAccount.SCHEME_SIP and PhoneAccount.SCHEME_TEL*/
TripmateConnectionService.SCHEME_AG, "userId", null), extras)
Need to mention, that in my BroadcastReceiver implementation I can detect incoming calls from other apps, so it's seems that I handling call detection correct and the call from above code is never really send to device of the user it was intended to.
Now is the question. I feel like I missing something vital. How exactly do devices with same app can communicate to each other without phone number? Does it really enough just to pass a user name to a telecomManager.placeCall and it should somehow manage to find right device with installed app and make a call to it? How can telecomManager distinguish where to make a call?
Sorry for unclear question, it's my first time doing something related to calls and I feel I luck understanding of the subject and it hard to make a question more concrete because I don't exactly know what am I missing.
I will put below some code I'm using now
Start an outgoing call
private fun placeSystemCall(myUid: String, peerUid: String, channel: String, role: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val extras = Bundle()
extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
val extraBundle = Bundle()
extraBundle.putString(Constants.CS_KEY_UID, myUid)
extraBundle.putString(Constants.CS_KEY_SUBSCRIBER, peerUid)
extraBundle.putString(Constants.CS_KEY_CHANNEL, channel)
extraBundle.putInt(Constants.CS_KEY_ROLE, Constants.CALL_ID_OUT)
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extraBundle)
try {
val telecomManager = applicationContext.getSystemService(TELECOM_SERVICE) as TelecomManager
val pa: PhoneAccount = telecomManager.getPhoneAccount(
config().phoneAccountOut?.accountHandle)
extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, pa.accountHandle)
telecomManager.placeCall(Uri.fromParts(
TripmateConnectionService.SCHEME_AG, peerUid, null), extras)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
In ConnectionService
override fun onCreateOutgoingConnection(phoneAccount: PhoneAccountHandle?, request: ConnectionRequest): Connection {
Log.i(TAG, "onCreateOutgoingConnection: called. $phoneAccount $request")
val extras = request.extras
val uid = extras.getString(Constants.CS_KEY_UID) ?: "0"
val channel = extras.getString(Constants.CS_KEY_CHANNEL) ?: "0"
val subscriber = extras.getString(Constants.CS_KEY_SUBSCRIBER) ?: "0"
val role = extras.getInt(Constants.CS_KEY_ROLE)
val videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE)
val connection = TripmateConnection(applicationContext, uid, channel, subscriber, role)
connection.setVideoState(videoState)
connection.setAddress(Uri.fromParts(SCHEME_AG, subscriber, null), TelecomManager.PRESENTATION_ALLOWED)
connection.setCallerDisplayName(subscriber, TelecomManager.PRESENTATION_ALLOWED)
connection.setRinging()
TMApplication.getInstance().config().setConnection(connection)
return connection
}
creating PhoneAccounts
private fun registerPhoneAccount(context: Context) {
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as? TelecomManager
?: throw RuntimeException("cannot obtain telecom system service")
val accountHandleIn = PhoneAccountHandle(
ComponentName(context, TripmateConnectionService::class.java), Constants.PA_LABEL_CALL_IN)
val accountHandleOut = PhoneAccountHandle(
ComponentName(context, TripmateConnectionService::class.java), Constants.PA_LABEL_CALL_OUT)
try {
var paBuilder: PhoneAccount.Builder = PhoneAccount.builder(accountHandleIn, Constants.PA_LABEL_CALL_IN)
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
val phoneIn = paBuilder.build()
paBuilder = PhoneAccount.builder(accountHandleOut, Constants.PA_LABEL_CALL_OUT)
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
val extra = Bundle()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
extra.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
paBuilder.setExtras(extra)
}
val phoneOut = paBuilder.build()
telecomManager.registerPhoneAccount(phoneIn)
telecomManager.registerPhoneAccount(phoneOut)
if (telecomManager.getPhoneAccount(phoneIn.accountHandle) == null || telecomManager.getPhoneAccount(phoneOut.accountHandle) == null) {
throw RuntimeException("cannot create account");
}
mCallSession = TripmateCallSession()
mCallSession?.phoneAccountIn = phoneIn
mCallSession?.phoneAccountOut = phoneOut
} catch (e: SecurityException) {
throw RuntimeException("cannot create account", e);
}
}
Thank you for your time! Any suggestions and links that could help me to understand more will be highly appreciated!

How to get directory's uuid using StorageManager on Android API below 26?

I created a helper function to check the remaining space of any given directory.
#RequiresApi(Build.VERSION_CODES.O)
fun Context.hasFreeSpace(directory: File, requiredStorageSpace: Long): Boolean{
return try {
val storageManager = getSystemService<StorageManager>()
val directoryUUID = storageManager!!.getUuidForPath(directory)
val availableBytes = storageManager.getAllocatableBytes(directoryUUID)
availableBytes > requiredStorageSpace
}catch (e: Exception){
e.printStackTrace()
false
}
}
Follow this link actually.
https://developer.android.com/training/data-storage/app-specific#query-free-space
The problem is I get storageManager!!.getUuidForPath and storageManager.getAllocatableBytes both require for API >= 26.
I did google around but not thing came back on how to get the directory's UUID on API < 26.
Does anyone have any idea how to achieve that?
Thanks
Well, I guess I need a different approach. As I googled, UUID required was added when Android O was released. So basically, no such thing gets directory UUID exits before O. This is my helper function now.
#SuppressLint("NewApi")
fun Context.hasFreeSpace(directory: File, requiredStorageSpace: Long): Boolean {
return try {
val api = Build.VERSION.SDK_INT
val availableBytes = when {
api >= Build.VERSION_CODES.O -> {
val storageManager = getSystemService<StorageManager>()
val directoryUUID = storageManager!!.getUuidForPath(directory)
storageManager.getAllocatableBytes(directoryUUID)
}
else -> {
val stat = StatFs(directory.path)
stat.availableBlocksLong * stat.blockSizeLong
}
}
availableBytes > requiredStorageSpace
} catch (e: Exception) {
e.printStackTrace()
false
}
}

MessagingStyle Not Displaying Text

New to Android development and I’m trying out the latest addHistoricMessage, and I’m missing something because it’s not displaying anything. On rare occasion the addMessage text is displayed, but the addHistoricMessage is never displayed. addMessage works consistently when using NotificationCompat, but NotificationCompat doesn’t appear to have addHistoricMessage.
Any thoughts appreciated - using androidx.appcompat:appcompat:1.0.2 and compileSdkVersion and targetSdkVersion are both 28.
An example of what I’m seeing is:
Test button that calls notification:
fun test(view: View) {
val job = GlobalScope.launch {
val repository = DataRepository.getInstance(Db.getDb(this#MainActivity))
AlarmReceiver().notifyTest(
this#MainActivity,
repository.upcomingDetail(9),
arrayListOf("Hi!", "Miss you!", "Hello!")
)
}
}
Notification methods and related (less important code removed):
fun notifyTest(context: Context, upcoming: UpcomingDetail, top3Sent: List<String>?) {
//...
#TargetApi(Build.VERSION_CODES.P)
when (Build.VERSION.SDK_INT) {
in 1..27 -> {
with(NotificationManagerCompat.from(context)) {
notify(upcoming.id.toInt(), legacyNotificationBuilder(
context,
upcoming,
noteIntent,
contentPending,
disablePending,
deletePending,
postponePending,
top3Sent
).build())
}
}
else -> context.getSystemService(NotificationManager::class.java)
.notify(upcoming.id.toInt(), notificationBuilder(
context,
upcoming,
noteIntent,
contentPending,
disablePending,
deletePending,
postponePending,
top3Sent
).build())
}
}
#RequiresApi(Build.VERSION_CODES.P)
private fun notificationBuilder(
context: Context,
upcoming: UpcomingDetail,
noteIntent: Intent,
contentPending: PendingIntent,
deletePending: PendingIntent,
disablePending: PendingIntent,
postponePending: PendingIntent,
top3Sent: List<String>?
): Notification.Builder {
val recipient: android.app.Person = android.app.Person.Builder().setName("Darren").setImportant(true).build()
val you: android.app.Person? = null
val messageStyle = Notification.MessagingStyle(recipient)
val message1 = Notification.MessagingStyle.Message("Hello!", Instant.now().minusSeconds(10 * 60).toEpochMilli(), recipient)
messageStyle.addHistoricMessage(message1)
messageStyle.addMessage(Notification.MessagingStyle.Message("Hi", Instant.now().toEpochMilli(), recipient))
val remoteInput: android.app.RemoteInput = android.app.RemoteInput.Builder(upcoming.id.toString()).run {
top3Sent?.let { setChoices(top3Sent.toTypedArray()) }
build()
}
//...
val inputAction = Notification.Action.Builder(0, context.getString(R.string.button_edit), inputPending).run {
addRemoteInput(remoteInput)
build()
}
return Notification.Builder(context, "Input").apply {
setSmallIcon(R.drawable.ic_stat)
style = messageStyle
setAutoCancel(true)
setCategory(Notification.CATEGORY_REMINDER)
setColor(ContextCompat.getColor(context, R.color.secondaryDarkColor))
setContentIntent(contentPending)
setDeleteIntent(deletePending)
setGroup("notifications")
setOnlyAlertOnce(true)
setVisibility(Notification.VISIBILITY_PRIVATE)
addAction(inputAction)
}
}
This is the behavior of historic message
Historic message is not normally shown at the notification. It is a special message that is only shown when user is replying through a RemoteInput. See the image above to see the behaviour. It should only be used when the message is not the main subject of the notification but may give context to a conversation.
Reference: Android MessagingStyle Notification As Clear As Possible

Categories

Resources