I need to develop an app which monitors a list of apps and, if the system is rebooted, return to the last open page of the monitored apps. So, say that android was on the youtube app playing lofi hip hop radio - beats to relax/study to. How could my app send android back to this given video? Also, is there a way for me to know where in the app the user is? For example, can I know that the user was on youtube playing lofi and open it back again for them?
I already know how to open another app as well as detect whichever app is currently open, but I need to know the its uri as well. I can use adb and shell script if it is needed.
I have the following code to launch another app:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val intent = Intent(this, BackAppListenerService::class.java)
startService(intent)
openApp(this, "com.google.android.youtube")
}
/** Open another app.
* #param context current Context, like Activity, App, or Service
* #param packageName the full package name of the app to open
* #return true if likely successful, false if unsuccessful
*/
fun openApp(context: Context, packageName: String?): Boolean {
val manager = context.packageManager
return try {
val i = manager.getLaunchIntentForPackage(packageName!!)
if (i == null) {
println("Activity not found")
return false;
//throw new ActivityNotFoundException();
}
//throw new ActivityNotFoundException();
i.addCategory(Intent.CATEGORY_LAUNCHER)
context.startActivity(i)
true
} catch (e: ActivityNotFoundException) {
println(e)
false
}
}
}
And with this I get the current foreground app
class BackAppListenerService : Service() {
private var isRunning = false
private var lastApp = ""
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate() {
isRunning = true
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable {
while (true) {
try {
Thread.sleep(10)
} catch (e: Exception) {
}
val currentForegroundApp = getForegroundApp()
val currentApp = currentForegroundApp.first
if (currentApp != lastApp) {
// New app on front
lastApp = currentApp
println("Current App $lastApp")
}
}
}).start()
return START_STICKY
}
// Must Have Usage Access Permission
fun getForegroundApp(): Pair<String, UsageStats> {
var currentApp = "NULL"
var currentAppInfo: UsageStats? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val usm = this.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val appList =
usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time)
if (appList != null && appList.size > 0) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap<Long, UsageStats>()
for (usageStats in appList) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
currentAppInfo = mySortedMap[mySortedMap.lastKey()]!!
currentApp = mySortedMap[mySortedMap.lastKey()]!!.packageName
}
}
} else {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.runningAppProcesses
currentApp = tasks[0].processName
}
return Pair(currentApp.split(".").last(), currentAppInfo!!)
}
[...]
}
Any help is much appreciated.
Related
Overview:
Hey there,
I am making an App which is tracking the calls on the phone. The way I want to do this when A call is incoming or outgoing I am making an API request and storing the information of the call. Once the call is ended I am again making API request for marking the call end. So basically I am making 2 API calls per call. I want to perform this operation even the app is closed.
I am using phone_plus package from pub.dev for tracking and getting the call information. When I looked into the package it's using Method Channel Internally for doing this operation. The plugin works perfectly when the app is closed also. It's giving the call status and the details I want
Problem : The problem is when App is closed or phone is locked I am not able to make the API request.
The following error is shown :
W/FlutterJNI: Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: com.morabaa.phone_plus. Response ID: 15
I have two important questions.
If the app is running in the background do I compulsory need to show the notification that app is running in the background
How can I make API calls in the background. I have tried with flutter_background_services package but the documentation is not clear. I didn't understand how should I pass my arguments to the background.
Can I directly clone the package code and paste my API calling function in native code. If so How can I do that? The package is written in Kotlin.
here is the code of the package :
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.TelephonyManager
import java.net.URI
import java.net.URLEncoder
import androidx.annotation.NonNull
import io.flutter.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.util.*
import kotlin.collections.ArrayList
/** PhonePlusPlugin */
class PhonePlusPlugin: FlutterPlugin, MethodCallHandler, BroadcastReceiver() {
private lateinit var context: Context
companion object {
lateinit var methodChannel: MethodChannel
private var lastState = TelephonyManager.EXTRA_STATE_IDLE
private var isIncoming = false
}
private fun setup(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
val plugin = PhonePlusPlugin()
methodChannel = MethodChannel(binding.binaryMessenger, "com.morabaa.phone_plus")
plugin.context = binding.applicationContext
methodChannel.setMethodCallHandler(plugin)
}
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
setup(binding = flutterPluginBinding)
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method.equals("phoneTest.incomingCallReceived")) {
Log.d("CallObserver", "phoneIncoming Test implementation")
// TODO: test mode with seconds to wait as parameter
} else {
result.notImplemented()
}
}
private val arguments: HashMap<String, Any> = HashMap()
#SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context?, intent: Intent) {
Log.d("CallObserver", "CallReceiver is starting ....")
var keyList: List<String?> = ArrayList()
val bundle = intent.extras
if (bundle != null) {
keyList = ArrayList(bundle.keySet())
Log.e("CallObserver", "keys : $keyList")
}
if (keyList.contains("incoming_number")) {
val phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
val phoneIncomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
val phoneOutgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
val phoneNumber = phoneOutgoingNumber ?: (phoneIncomingNumber
?: "")
if (phoneState != null && phoneNumber != "") {
if (lastState == phoneState) {
//No change, debounce extras
return
}
Log.d("CallObserver", "State Changed in here>>>>>> $phoneState")
arguments["Date"] = System.currentTimeMillis()
if(phoneNumber!="") arguments["Number"] = phoneNumber
if (TelephonyManager.EXTRA_STATE_RINGING == phoneState) {
isIncoming = true
//
lastState = TelephonyManager.EXTRA_STATE_RINGING
onIncomingCallReceived()
} else if (TelephonyManager.EXTRA_STATE_IDLE == phoneState) {
if (lastState == TelephonyManager.EXTRA_STATE_RINGING) {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE
onMissedCall()
} else {
if (isIncoming) {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE
onIncomingCallEnded()
} else {
//
lastState = TelephonyManager.EXTRA_STATE_IDLE
onOutgoingCallEnded()
}
}
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK == phoneState) {
isIncoming = lastState.equals(TelephonyManager.EXTRA_STATE_RINGING)
//
lastState = TelephonyManager.EXTRA_STATE_OFFHOOK
if(isIncoming) {
onIncomingCallAnswered()
} else {
onOutgoingCallStarted()
}
}
}
}
}
private fun onIncomingCallReceived() {
Log.d("CallObserver", "onIncomingCallReceived : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onIncomingCallReceived", arguments)
}
private fun onIncomingCallAnswered() {
Log.d("CallObserver", "onIncomingCallAnswered : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onIncomingCallAnswered", arguments)
}
private fun onIncomingCallEnded() {
Log.d("CallObserver", "onIncomingCallEnded : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onIncomingCallEnded", arguments)
}
private fun onOutgoingCallStarted() {
Log.d("CallObserver", "onOutgoingCallStarted : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onOutgoingCallStarted", arguments)
}
private fun onOutgoingCallEnded() {
Log.d("CallObserver", "onOutgoingCallEnded : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onOutgoingCallEnded", arguments)
}
private fun onMissedCall() {
Log.d("CallObserver", "onMissedCall : number is : ${arguments["Number"]}")
methodChannel.invokeMethod("onMissedCall", arguments)
}
}
This question already has answers here:
Android - implementing startForeground for a service?
(11 answers)
Closed 11 months ago.
I've createde a simple app (following this link How to Play Sound On Button Click in Android Studio Java 2021 ), and everything works fine, but, i need this app can still playing the sound after minimize then or block the phone (is a white noise app from help on sleep for my baby daughter, and i need that keep playing all night).
Is there any way or setting that allows the sound not to stop after minimizing or blocking the application?
Thank you for enveryone that could help me and my baby :)
If you add the ability to run your application in the background, it will solve your problem. Resources you can review:
https://developer.android.com/guide/components/services
https://developer.android.com/guide/background/threading
Basic Example :
AndroidManifest.xml :
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
*
*
*
*
<service android:name=".ui.main.AudioPlayerService"/>
</application>
AudioPlayerService :
private const val PLAYBACK_CHANNEL_ID = "blabla"
private const val PLAYBACK_NOTIFICATION_ID = 1
class AudioPlayerService : Service() {
private var player: SimpleExoPlayer? = null
private var playerNotificationManager: PlayerNotificationManager? = null
private val mediaItem: MediaItem = MediaItem.fromUri(ApiInterface.Channel24LiveURL)
/** Classes to be connected to the service with the
service
*reference providing the link between
*/
private val mBinder = AudioServiceBinder()
/**
* Data that the service will share to other classes. */
inner class AudioServiceBinder : Binder() {
val service
get() = this#AudioPlayerService
val player
get() = this#AudioPlayerService.player
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
/**
* Service start part
*/
override fun onCreate() {
super.onCreate()
// init player
player = SimpleExoPlayer.Builder(this)
.build().apply {
setMediaItem(mediaItem)
playWhenReady = true
prepare()
}
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
applicationContext,
PLAYBACK_CHANNEL_ID,
R.string.playback_channel_name,
R.string.playback_channel_desc,
PLAYBACK_NOTIFICATION_ID,
object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
return getString(R.string.def_playback_title)
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return PendingIntent.getActivity(
applicationContext,
0,
Intent(applicationContext, MainActivity::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
}
override fun getCurrentContentText(player: Player): CharSequence? {
return null
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return getBitmapFromVectorDrawable(applicationContext, R.mipmap.ic_launcher)
}
}, object : PlayerNotificationManager.NotificationListener {
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
stopSelf()
}
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
if (ongoing) {
// Make sure the service will not get destroyed while playing media.
startForeground(notificationId, notification)
} else {
// Make notification cancellable.
stopForeground(false)
}
}
}
).apply {
// previous and next actions.
setUseNavigationActions(true)
setPlayer(player)
}
}
#MainThread
private fun getBitmapFromVectorDrawable(
context: Context,
#Suppress("SameParameterValue") #DrawableRes drawableId: Int
): Bitmap? {
return ContextCompat.getDrawable(context, drawableId)?.let {
val drawable = DrawableCompat.wrap(it).mutate()
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
}
}
#MainThread
fun changePlayerVolume(view: ImageView) {
player?.let {
if (it.volume == 1.0f) {
it.volume = 0.0f
view.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_volume_off))
} else {
it.volume = 1.0f
view.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_volume_up))
}
}
}
#MainThread
fun jumpLiveStream() {
player?.let {
it.setMediaItem(mediaItem)
it.playWhenReady = true
}
}
/**
* Triggered when the app is closed. */
override fun onTaskRemoved(rootIntent: Intent?) {// Stop the service when the user closes the application.l
releasePlayer()
stopSelf()
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
releasePlayer()
stopSelf()
super.onDestroy()
}
/**
* delete player and playerNotificationManager */
private fun releasePlayer() {
player?.let {
it.release()
player = null
}
playerNotificationManager?.let {
it.setPlayer(null)
playerNotificationManager = null
}
}
}
Good luck!
I am currently implement a feature where the users are requested to ignore battery optimisation for the application. The reason for doing so, is that the main functionality of the application is unfortunately drastically affected by power save mode.
To achieve my goal, I prompt the users by creating an Intent and setting the Action to ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.
Although, before firing the Intent, I both check for isPowerSaveMode() and isIgnoringBatteryOptimizations() to ensure that I don't prompt the users when power save mode is not enabled; which is a requirement for the feature. The way I do so is by:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
boolean isPowerSaveMode = pm.isPowerSaveMode(); // always returns false for Huawei devices
This works fine for the most devices, but for Huawei devices, isPowerSaveMode() always returns false. Consequently, since the preconditions fail, the prompt is never shown.
Has anyone else possibly encountered this issue? If so, what did you do to solve it?
As a note, the same issue is also present in the Xamarin.Android SDK.
Some Chinese ROM like Huawei or Xiaomi didn't implement the standard API for power save mode query. But like other system settings, a state flag will be saved to database when user turn power save mode on/off.
So we can utilize this state flag to solve the compatibility problem. Also a specific intent will send by system when toggle power save mode, we can listen this intent action to monitor power save mode changing.
Below is the detailed kotlin code implementation for Huawei or Xiaomi devices.
object PowerManagerCompat {
private const val TAG = "PowerManagerCompat"
interface PowerSaveModeChangeListener {
/**
* will be called when power save mode change, new state can be query via [PowerManagerCompat.isPowerSaveMode]
*/
fun onPowerSaveModeChanged()
}
private val POWER_SAVE_MODE_VALUES = mapOf(
"HUAWEI" to 4,
"XIAOMI" to 1
)
private val POWER_SAVE_MODE_SETTING_NAMES = arrayOf(
"SmartModeStatus", // huawei setting name
"POWER_SAVE_MODE_OPEN" // xiaomi setting name
)
private val POWER_SAVE_MODE_CHANGE_ACTIONS = arrayOf(
"huawei.intent.action.POWER_MODE_CHANGED_ACTION",
"miui.intent.action.POWER_SAVE_MODE_CHANGED"
)
private const val monitorViaBroadcast = true
/**
* Monitor power save mode change, only support following devices
* * Xiaomi
* * Huawei
*/
fun monitorPowerSaveModeChange(context: Context, powerSaveModeChangeListener: PowerSaveModeChangeListener) {
if (Build.MANUFACTURER.toUpperCase(Locale.getDefault()) !in POWER_SAVE_MODE_VALUES.keys) {
Log.w(TAG, "monitorPowerSaveModeChange: doesn't know how to monitor power save mode change for ${Build.MANUFACTURER}")
}
if (monitorViaBroadcast) {
context.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
powerSaveModeChangeListener.onPowerSaveModeChanged()
}
}, IntentFilter().also {
for (a in POWER_SAVE_MODE_CHANGE_ACTIONS) {
it.addAction(a)
}
})
} else {
val contentObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
powerSaveModeChangeListener.onPowerSaveModeChanged()
}
}
for (name in POWER_SAVE_MODE_SETTING_NAMES) {
context.contentResolver.registerContentObserver(
Uri.parse("content://settings/system/${name}"), false, contentObserver)
}
}
}
/**
* Check the system is currently in power save mode
* #see [PowerManager.isPowerSaveMode]
*/
fun isPowerSaveMode(context: Context): Boolean {
if (Build.MANUFACTURER.toUpperCase(Locale.getDefault()) in POWER_SAVE_MODE_VALUES.keys) {
return isPowerSaveModeCompat(context)
}
val powerManager = context.getSystemService(Context.POWER_SERVICE) as? PowerManager
return powerManager?.isPowerSaveMode ?: false
}
private fun isPowerSaveModeCompat(context: Context): Boolean {
for (name in POWER_SAVE_MODE_SETTING_NAMES) {
val mode = Settings.System.getInt(context.contentResolver, name, -1)
if (mode != -1) {
return POWER_SAVE_MODE_VALUES[Build.MANUFACTURER.toUpperCase(Locale.getDefault())] == mode
}
}
return false
}
}
Each oem modifies the SDK to suit their needs . Huawei devices don't use the default power saver function , instead they use something called "Protected apps". Protected apps are set of apps which are allowed to run even when the screen is turned off. So that's the reason it always returns false . Its better to throw a intent to protected apps screen but there is no way to know if your app is added to the protected apps list.
What is protected apps ?
I've found a way to manually request current Huawei Power Mode state and receive change events by adding a custom action to the IntentFilter:
(Note tested only on Huawei P20 Lite (ANE-LX3) # EMUI 8.0.0)
// Manually request Power Save Mode:
public Boolean isPowerSaveMode(Context context) {
if (Build.MANUFACTURER.equalsIgnoreCase("Huawei")) {
return isPowerSaveModeHuawei(context);
} else {
return isPowerSaveModeAndroid(context);
}
}
#TargetApi(21)
private Boolean isPowerSaveModeAndroid(Context context) {
boolean isPowerSaveMode = false;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm != null) isPowerSaveMode = pm.isPowerSaveMode();
}
return isPowerSaveMode;
}
private Boolean isPowerSaveModeHuawei(Context context) {
try {
int value = android.provider.Settings.System.getInt(context.getContentResolver(), "SmartModeStatus");
return (value == 4);
} catch (Settings.SettingNotFoundException e) {
// Setting not found? Return standard android mechanism and hope for the best...
return isPowerSaveModeAndroid(context);
}
}
// Listening for changes in Power Save Mode
public void startMonitoringPowerSaveChanges(Context context) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mPowerSaveChangeReceiver != null) {
return;
}
// Register for PowerSaver change updates.
mPowerSaveChangeReceiver = new PowerSaveChangeReceiver();
// Registering the receiver
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
// Add custom huawei action
filter.addAction("huawei.intent.action.POWER_MODE_CHANGED_ACTION");
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
filter.addAction(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
}
context.registerReceiver(mPowerSaveChangeReceiver, filter);
}
}
#TargetApi(21)
class PowerSaveChangeReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
boolean isPowerSaveMode = false;
// Oh, Huawei...why don't you play by the same rules as everyone else?
if (intent.getAction().equals("huawei.intent.action.POWER_MODE_CHANGED_ACTION")) {
Bundle extras = intent.getExtras();
if ((extras != null) && extras.containsKey("state")) {
int state = intent.getExtras().getInt("state");
isPowerSaveMode = (state == 1); // ON=1; OFF=2
}
} else {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
isPowerSaveMode = pm.isPowerSaveMode();
}
Log.d("MyTag", "[powersavechange] isPowerSaveMode? " + isPowerSaveMode);
}
}
I have faced new the same problem while inmplementation handheld and wearable devices.
The only solution I found is to disable battery saver mode for all apps.
I would suggest to detect the result of your methods after disabling such mode for all apps. This bug appear only on Huawei. Awful vendor.
private void isPowerSaveModeHuaweiXiaomi(){
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
try {
int value = android.provider.Settings.System.getInt(getContext().getContentResolver(), "POWER_SAVE_MODE_OPEN");
} catch (Settings.SettingNotFoundException e) {
Log.d("Valor modo bateria:", "Error");
}
}else if (Build.MANUFACTURER.equalsIgnoreCase("Huawei")){
try {
int value = android.provider.Settings.System.getInt(getContext().getContentResolver(), "SmartModeStatus");
} catch (Settings.SettingNotFoundException e) {
Log.d("Valor modo bateria:", "Error");
}
}
}
On new Huawei devices such as Huawei P30 lite for instance the solution to this question is unknown as for now (27.12.2021). calling getInt with the key "SmartModeStatus" will throw a key unknown exception. Therefore the best we can do is the following.
private string HuaweiPowerSaveModeSettingsName = "SmartModeStatus";
private int HuaweiPowerSaveModeValue = 4;
public bool IsBatterySaverEnabled
=> Build.Manufacturer?.ToUpper() == "HUAWEI" ? GetIsBatterySaverEnabledHuawei() : GetIsBatterySaverEnabledAllDevicesExceptHuawei();
private bool GetIsBatterySaverEnabledAllDevicesExceptHuawei()
{
return PowerManager.FromContext(Application.Context)?.IsPowerSaveMode ?? false;
}
private bool GetIsBatterySaverEnabledHuawei()
{
try
{
var mode = Settings.System.GetInt(Application.Context.ContentResolver, HuaweiPowerSaveModeSettingsName);
return HuaweiPowerSaveModeValue == mode;
} catch (Exception e)
{
return GetIsBatterySaverEnabledAllDevicesExceptHuawei();
}
}
For huawei vtr-al00, SmartModeStatus 1 could be ultra save mode or the normal mode. I've used reflection to handle this.
final int _HX = Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")?2
:Build.MANUFACTURER.equalsIgnoreCase("Huawei")?1
:0;
// “No Kotlin”
private boolean isPowerSaveModeCompat(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& powerManager.isPowerSaveMode()) { // hopefully...
return true;
}
if (_HX==0) {
return false;
}
else if (_HX==1) {
try {
int value = Settings.System.getInt(getContentResolver(), "SmartModeStatus");
CMN.debug("isPowerSaveModeCompat::huawei::"+value);
// value 4==Save Mode; 1==Ultra Save Mode==Normal Mode;
// ( tested on my huawei vtr-al00 )
if(value==4) {
return true;
}
if(value==1) {
// what if Ultra save mode???
// https://github.com/huaweigerrit
// https://github.com/SivanLiu/HwFrameWorkSource
// https://stackoverflow.com/questions/2641111/where-is-android-os-systemproperties
// Class sysProp= Class.forName("android.os.SystemProperties");
// Method sysProp_getBool = sysProp.getMethod("getBoolean", new Class[]{String.class, boolean.class});
// Object[] parms = new Object[]{"sys.super_power_save", false};
// CMN.debug("huawei::UltraPowerSave::", sysProp_getBool.invoke(null, parms));
// CMN.debug("huawei::UltraPowerSave::", getSystemProperty("sys.super_power_save"));
return "true".equals(getSystemProperty("sys.super_power_save"));
}
} catch (Exception e) {
CMN.debug(e);
}
}
else if (_HX==2){
try {
int value = Settings.System.getInt(getContentResolver(), "POWER_SAVE_MODE_OPEN");
CMN.debug("isPowerSaveModeCompat::xiaomi::"+value);
// dont have xiaomi. not tested.
return value==1;
} catch (Exception e) {
CMN.debug(e);
}
}
// else if...
return false;
}
// https://stackoverflow.com/questions/9937099/how-to-get-the-build-prop-values
public String getSystemProperty(String key) {
String value = null;
try {
value = (String) Class.forName("android.os.SystemProperties")
.getMethod("get", String.class).invoke(null, key);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
Java is just shorter kotlin, even with so many comments and dirty tests!
:)
I am working on an Application that requires the user to be able to geo-tag picture taken via the camera. The application is to encourage user geo-tag trees to encourage afforestation and reduce global warming.
Is it possible to implement this feature and how can I implement it?
NB: This is my first major project.
Creating a comfortable user experience with location is a big task. These are some of the concerns you'll have to address:
The user may have disabled location globally.
The app may not have the permission to use location.
You want to ask the user for permission and/or to enable location.
You don't want to bother the user by asking over and over again.
Asking for permission means pausing your activity in favor of a system activity that asks for permission. There's a similar but distinct mechanism to ask to enable the location.
Your activity will resume when the user responds, you need to override a special callback method that receives the result.
It's not good to try to fetch location just when you need it. It may take time. Instead you have to ask Android at the outset to push location updates to you.
The user may revoke the location permission while using the app, you must constantly re-check the permissions, be resilient to sudden exceptions, and repeat the request to receive location updates when the location permission comes back.
Here are some snippets from my project that can help you get started:
private val Context.fusedLocationProviderClient get() = getFusedLocationProviderClient(this)
private suspend fun FusedLocationProviderClient.tryFetchLastLocation(): Location? =
lastLocation.await()
?.also { info { "Got response from getLastLocation()" } }
?: run { warn { "getLastLocation() returned null" }; null }
suspend fun Context.canUseLocationFg() =
appHasLocationPermission() &&
locationSettingsException(locationRequestFg, locationRequestBg) == null
import android.Manifest.permission.ACCESS_FINE_LOCATION
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
fun Context.appHasLocationPermission() =
checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
suspend fun Context.locationSettingsException(
vararg locationRequests: LocationRequest
): ApiException? = try {
getSettingsClient(this)
.checkLocationSettings(LocationSettingsRequest.Builder()
.addAllLocationRequests(locationRequests.asList()).build())
.await()
null
} catch (e: ApiException) { e }
const val CODE_REQUEST_FINE_LOCATION = 13
const val CODE_RESOLVE_API_EXCEPTION = 14
suspend fun Fragment.checkAndCorrectPermissionsAndSettings() {
with(context!!) {
if (!appHasLocationPermission()) {
warn { "FG: our app has no permission to access fine location" }
delay(WAIT_MILLISECONDS_BEFORE_ASKING)
if (!appHasLocationPermission()) {
startIntentRequestLocationPermission()
return
}
}
if (locationSettingsException(locationRequestFg, locationRequestBg) == null) return
warn { "FG: ResolvableApiException for location request (probably location disabled)" }
if (!mainPrefs.shouldAskToEnableLocation) return
delay(WAIT_MILLISECONDS_BEFORE_ASKING)
locationSettingsException(locationRequestFg, locationRequestBg)
?.let { it as? ResolvableApiException }
?.also { startIntentResolveException(it) }
}
}
fun Fragment.startIntentRequestLocationPermission() =
requestPermissions(arrayOf(ACCESS_FINE_LOCATION), CODE_REQUEST_FINE_LOCATION)
fun Fragment.startIntentResolveException(e: ResolvableApiException) =
startIntentSenderForResult(e.resolution.intentSender, CODE_RESOLVE_API_EXCEPTION, null, 0, 0, 0, null)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode != CODE_REQUEST_FINE_LOCATION) return
permissions.zip(grantResults.asList())
.find { (perm, _) -> perm == ACCESS_FINE_LOCATION }
?.also { (_, result) ->
if (result == PermissionChecker.PERMISSION_GRANTED) {
info { "User has granted us the fine location permission" }
} else {
warn { "User hasn't granted us the fine location permission (grant result: ${grantResults[0]})" }
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != CODE_RESOLVE_API_EXCEPTION) return
if (resultCode == Activity.RESULT_OK) {
info { "ResolvableApiException is now resolved" }
} else {
warn { "ResolvableApiException resolution failed with code $resultCode" }
activity!!.mainPrefs.applyUpdate { setShouldAskToEnableLocation(false) }
}
}
val locationRequestFg = LocationRequest().apply {
interval = 1000
fastestInterval = 10
priority = PRIORITY_BALANCED_POWER_ACCURACY
}
suspend fun Context.receiveLocationUpdatesFg(locationState: LocationState) {
fusedLocationProviderClient.apply {
tryFetchLastLocation()?.also {
info { "lastLocation: ${it.description}" }
locationState.location = it
}
LocationCallbackFg.locationState = locationState
requestLocationUpdates(locationRequestFg, LocationCallbackFg, null).await()
info(CC_PRIVATE) { "FG: started receiving location updates" }
}
}
object LocationCallbackFg : LocationCallback() {
var locationState: LocationState? = null
override fun onLocationResult(result: LocationResult) {
val lastLocation = result.lastLocation
info { "FG: received location ${lastLocation.description}" }
locationState?.apply { location = lastLocation }
?: warn { "LocationCallbackFg received an event while not in use" }
}
}
The code relies on Task.await(), this is in Kotlin's library kotlinx-coroutines-play-services.
If I understand well, once ARCore 1.0 will be released on Google Play, it will be necessary to install it on the device in order to be able to run an ARCore app.
How to check if ARCore lib/apk is installed on device ?
Should be sufficient to do something like:
try {
arCoreSession = Session(this)
val config = Config(arCoreSession)
if (!arCoreSession.isSupported(config)) {
Logger.d("ARCore not installed")
} else {
arCoreSession.configure(config)
}
} catch (ex: Throwable) {
Logger.d("ARCore not installed")
}
This is what I'm using here for one my apps and works fine on devices with or wothout ARCore.
According to ARCore documentation 1.4.0, if optional it is important check its availability recursively and then install it:
void maybeEnableArButton() {
// Likely called from Activity.onCreate() of an activity with AR buttons.
ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
if (availability.isTransient()) {
// re-query at 5Hz while we check compatibility.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
maybeEnableArButton();
}
}, 200);
}
if (availability.isSupported()) {
mArButton.setVisibility(View.VISIBLE);
mArButton.setEnabled(true);
// indicator on the button.
} else { // unsupported or unknown
mArButton.setVisibility(View.INVISIBLE);
mArButton.setEnabled(false);
}
}
If already supported just check if ARCore is installed:
// Set to true ensures requestInstall() triggers installation if necessary.
private boolean mUserRequestedInstall = true;
// in onResume:
try {
if (mSession == null) {
switch (ArCoreApk.getInstance().requestInstall(this, mUserRequestedInstall)) {
case INSTALLED:
mSession = new Session(this);
// Success.
break;
case INSTALL_REQUESTED:
// Ensures next invocation of requestInstall() will either return
// INSTALLED or throw an exception.
mUserRequestedInstall = false;
return;
}
}
} catch (UnavailableUserDeclinedInstallationException e) {
// Display an appropriate message to the user and return gracefully.
return;
} catch (...) { // current catch statements
...
return; // mSession is still null
}
Sometimes it is easier to request this with Rx methodology. Here's the code:
private fun getArAvailabilityRx(context: Context): Single<ArCoreApk.Availability> {
return Single.fromCallable<ArCoreApk.Availability> {
ArCoreApk.getInstance().checkAvailability(context)
}.flatMap { availability ->
if (availability.isTransient) {
// `isTransient` means it hasn't finished loading value; let's request the value in 500 ms
getArAvailabilityRx(context).delaySubscription(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
} else {
Single.just(availability)
}
}.observeOn(AndroidSchedulers.mainThread())
}
Here's a little utility class I wrote (based originally on something from https://github.com/google/helloargdx).
It will perform all the checks and setup necessary, in order to ensure it is safe to launch a Session.
abstract class ArCheckFragment : Fragment() {
private var userRequestedInstall = true
abstract fun onCameraPermissionDeny()
abstract fun onArCoreUnavailable(availability: Availability)
abstract fun onArCoreInstallFail(exception: UnavailableException)
abstract fun onArCoreInstallSuccess()
override fun onResume() {
super.onResume()
performCheck()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_CAMERA_PERMISSION) {
for (i in permissions.indices) {
if (permissions[i] == Manifest.permission.CAMERA &&
grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
checkArCore()
return
}
}
onCameraPermissionDeny()
}
}
/**
* Performs the whole check
*/
fun performCheck() {
if (requestCameraPermission()) {
checkArCore()
}
}
/**
* Requests the camera permission, if necessary.
* #return whether camera permission is already granted. If so, the permission won't be requested.
*/
private fun requestCameraPermission(): Boolean {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
return true
}
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA_PERMISSION)
return false
}
private fun checkArCore() {
if (!isResumed) {
return
}
val availability = ArCoreApk.getInstance().checkAvailability(activity)
if (availability.isTransient) {
requireView().postDelayed(AR_CORE_CHECK_INTERVAL) { checkArCore() }
return
}
when (availability) {
Availability.SUPPORTED_INSTALLED ->
onArCoreInstallSuccess()
Availability.SUPPORTED_APK_TOO_OLD,
Availability.SUPPORTED_NOT_INSTALLED ->
startArCoreInstallation()
else ->
onArCoreUnavailable(availability)
}
}
private fun startArCoreInstallation() {
try {
val installStatus =
ArCoreApk.getInstance().requestInstall(activity, userRequestedInstall)
when (installStatus) {
InstallStatus.INSTALLED -> onArCoreInstallSuccess()
InstallStatus.INSTALL_REQUESTED,
null ->
// Ensures next invocation of requestInstall() will either return
// INSTALLED or throw an exception.
userRequestedInstall = false
}
} catch (exception: UnavailableException) {
onArCoreInstallFail(exception)
}
}
companion object {
private const val REQUEST_CODE_CAMERA_PERMISSION = 1
private const val AR_CORE_CHECK_INTERVAL = 200L
}
}
You can subclass this Fragment and implement the abstract functions to receive callbacks on what the result of these checks is. Only in onArCoreInstallSuccess is it safe to create a Session.