In my android application from my background service I want to launch activity with transparent background. Because of the fact that the application is transparent I always want it to be displayed on some other activity from my application. So before the launch I would need to somehow check if any activity is currently opened and if it isn't then open home activity and after that my transparent activity. But if application was already opened then I want to open new activity on top of most recent one.
How could I achieve that? Only thing I found is how to check if my application is in foreground or not. But when my app is in background I would still like to open my transparent activity atop most recent activity.
class FirebaseDbApplication : Application() {
override fun onCreate() {
super.onCreate()
}
companion object{
private var sIsChatActivityOpen = false
fun isChatActivityOpen() : Boolean {
return sIsChatActivityOpen
}
fun setChatActivityOpen(isChatActivityOpen: Boolean) {
this.sIsChatActivityOpen = isChatActivityOpen
}
}
}
// In your activity
override fun onResume() {
super.onResume()
FirebaseDbApplication.setChatActivityOpen(true)
}
override fun onPause() {
super.onPause()
FirebaseDbApplication.setChatActivityOpen(false)
}
// For checking is activity is opened or not
if (FirebaseDbApplication.isChatActivityOpen()) {
// Activity is opened
}
else{
// Activity is closed
}
Related
I am working on an app where I need to set security feature by setting passcode/password/pin for the user so that whenever they open the app from the background, the app asks for password/passcode/pin. I read a few articles/solutions like this but they didn't help me. The same functionality we found in normal banking apps in which whenever you open an app, they will ask for passcode/fingerprint.
I have already set up the logic to save the passcode/pin in shared preference but I am not sure about when to ask it. I know that we can't replace splash screen with passcode/pin activity, because sometimes from app's homeScreen/MainActivity, user press home button and when they again open the app from recent apps, the app should ask for passcode/pin to resume the app use.
Any help would be appreciated.
This is an interesting question, I will share my thought on this question and give a solution as well.
Terminology:
App Lock Type: A generic name for pin/pincode/password/passcode, etc. (in the following section, I will use the pin name to demonstrate)
PinActivity: A screen where users input their pin to verify themself
Story:
For apps that require users to input pin, they usually want to make sure sensitive information is not leaked/stolen by other people. So we will categorize app activities into 2 groups.
Normal activities: Doesn't contain any sensitive information, usually before users logged in to the app, such as SplashActivity, LoginActivity, RegistrationActivity, PinActivity, etc.
Secured activities: Contain sensitive information, usually after users logged in, such as MainActivity, HomeActivity, UserInfoActivity, etc.
Conditions:
For secured activities, we must make sure users always input their pin before viewing the content by showing the PinActivity. This activity will be shown in the following scenarios:
[1] When users open a secured activity form a normal activity, such as from SplashActivity to MainActivity
[2] When users open a secured activity by tapping on Notifications, such as they tap on a notification to open MainActivity
[3] When users tap on the app from the Recents screen
[4] When the app starts a secured activity from another place like Services, Broadcast Receiver, etc.
Implementation:
For case [1] [2] and [4], before start a secured activity we will add an extra to the original intent. I will create a file named IntentUtils.kt
IntentUtils.kt
const val EXTRA_IS_PIN_REQUIRED = "EXTRA_IS_PIN_REQUIRED"
fun Intent.secured(): Intent {
return this.apply {
putExtra(EXTRA_IS_PIN_REQUIRED, true)
}
}
Use this class from normal activities, notifications, services, etc.
startActivity(Intent(this, MainActivity::class.java).secured())
For case [3], I will use 2 APIs:
ProcessLifecycleOwner: To detect whether the app go to background. A typical scenario is when users click on Home/Menu key on their devices.
ActivityLifecycleCallbacks: To detect whether an activity is resumed by relying on onActivityResumed(activity) method.
First I create a base activity, all normal activitis must extend from this class
BaseActivity.kt
open class BaseActivity : AppCompatActivity() {
// This method indicates that a pin is required if
// users want to see the content inside.
open fun isPinRequired() = false
}
Second I create a secured activity, all secured activities must extend from this class
SecuredActivity.kt
open class SecuredActivity : BaseActivity() {
override fun isPinRequired() = true
// This is useful when launch a secured activity with
// singleTop, singleTask, singleInstance launch mode
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
}
}
Third I create a class that extends from the Application, all logic are inside this class
MyApplication.kt
class MyApplication : Application() {
private var wasEnterBackground = false
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl())
ProcessLifecycleOwner.get().lifecycle.addObserver(LifecycleObserverImpl())
}
private fun showPinActivity() {
startActivity(Intent(this, PinActivity::class.java))
}
inner class LifecycleObserverImpl : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onEnterBackground() {
wasEnterBackground = true
}
}
inner class ActivityLifecycleCallbacksImpl : ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
val baseActivity = activity as BaseActivity
if (!wasEnterBackground) {
// Handle case [1] [2] and [4]
val removed = removeIsPinRequiredKeyFromActivity(activity)
if (removed) {
showPinActivity()
}
} else {
// Handle case [3]
wasEnterBackground = false
if (baseActivity.isPinRequired()) {
removeIsPinRequiredKeyFromActivity(activity)
showPinActivity()
}
}
}
private fun removeIsPinRequiredKeyFromActivity(activity: Activity): Boolean {
val key = EXTRA_IS_PIN_REQUIRED
if (activity.intent.hasExtra(key)) {
activity.intent.removeExtra(key)
return true
}
return false
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {}
}
}
Conclusion:
This solution works for those cases that I mentioned before, but I haven't tested the following scenarios:
When start a secured activity has launch mode singleTop|singleTask|singleInstance
When application killed by the system on low memory
Other scenarios that someone might encounter (if yes please let me know in the comments section).
I'm trying to detect when my app was opened, put on foreground, put on background and closed.
I tried to do it like this:
ProcessLifecycleOwner.get().lifecycle.addObserver(ApplicationLifecycleTracker())
...
class ApplicationLifecycleTracker: LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
Timber.d("APP CONTROL - RESUMED")
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
Timber.d("APP CONTROL - PAUSED")
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onAppOpened() {
Timber.d("APP CONTROL - OPENED")
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onAppClosed() {
Timber.d("APP CONTROL - CLOSED")
}
}
So far, I can get these events right: ON_CREATE, ON_START and ON_STOP.
But ON_DESTROY is never called when I close my application.
How can I get the callback for ON_DESTROY correctly?
Actually your activity is not finished completely. System is temporarily destroying this instance of the activity to save space.
check with function isFinishing() for track the correct status of activity
more details refer onDestroy
When the user clicks on a button, it switches a boolean value in a sharedpreference object to true/false. When I access that state later on in the activity, the state is saved and works fine. However when I click on the Android's back button to pause the app, and resume the app again, the SharedPreference object is switched to true. Even though it was at false when I examined the onPause() method with a debugger.
Basically I've tried examining the state of the SharedPreference object in the onPause, onCreate, and onResume methods of my activity. I'm not sure why the value gets switched back to its default value (true) during the onCreate method.
override fun onPause() {
super.onPause()
val p = pauseButtonTracker.pauseButtonStateAtResume() // value is false
}
override fun onResume() {
super.onResume()
val q = pauseButtonTracker.pauseButtonStateAtResume() // value is switched to true
//...
// object that manages the shared preferences object I was talking about
class PauseButtonTracker(context: Context) {
private val PAUSE_BUTTON_TRACKER = "PAUSE_BUTTON_TRACKER"
private val WAS_AT_RESUME = "WAS_AT_RESUME"
private val pauseTracker = context.getSharedPreferences(PAUSE_BUTTON_TRACKER, 0)
private val pauseTrackerEditor = pauseTracker.edit()
fun pauseButtonStateAtResume(): Boolean{
return pauseTracker.getBoolean(WAS_AT_RESUME, true)
}
fun switchPauseButtonStateToPause(){
pauseTrackerEditor.putBoolean(WAS_AT_RESUME, false)
pauseTrackerEditor.apply()
}
fun switchPauseButtonStateToResume(){
pauseTrackerEditor.putBoolean(WAS_AT_RESUME, true)
pauseTrackerEditor.apply()
}
}
value contained in,
pauseButtonTracker.pauseButtonStateAtResume()
should've remained false, when onResume is called, yet it gets switched to true for some reason.
Woops, the solution was to just put a debugger on
fun switchPauseButtonStateToResume(){
pauseTrackerEditor.putBoolean(WAS_AT_RESUME, true)
pauseTrackerEditor.apply()
}
To see if it was getting called for whatever reason.
It turns out that it was being called from the main activity, right before it switches to the activity with the pause/resume button. Therefore if the user clicks pause, and exits out of the app, and resumes it again, it will always switch back to the resume state.
On first activity where onPause() is called ,
you should have called switchPauseButtonStateToPause
before val p = pauseButtonTracker.pauseButtonStateAtResume()
and then call onResume() method.
I have an activity with two fragments.
The second one is called when I click on something to the first.
What I want is this : if i click on "back" button, I want to go back to the first fragment (that is working), but I want to set the visibility to VISIBLE on an element (if the first fragment is called with back press only)
How do I do that ?
I tried something like this (in my main fragment), I've found the idea in another topic, but this is trigger always in my main activity :
override fun onResume() {
super.onResume()
view?.isFocusableInTouchMode = true
view?.requestFocus()
view?.setOnKeyListener { v, keyCode, event ->
if(event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "backpress pressed")
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
Temporary solution :
I've created a companion object with a value true or false and I change it everytime I need it, but it's temporary only.
Assuming your second Fragment replaces the first (i.e. using FragmentTransaction#replace), your first Fragment (we'll call them FragmentA and FragmentB) will be paused (i.e. onPause() will be called on FragmentA).
When you press the back button, the backstack will be popped, and FragmentA will be resumed (i.e. onResume() will be called).
What I would recommend, is to save a boolean flag in FragmentA, and set it to true when you show FragmentB. Then, in FragmentA#onResume, you can check if the flag is set to true, and set it back to false while handing the case that you wanted.
For example, something like:
private const val STATE_WAITING_FOR_FRAGMENT_B = "state_waiting_b"
class FragmentA : Fragment() {
private var isWaitingForFragmentB: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
isWaitingForFragmentB = savedInstanceState.getBoolean(STATE_WAITING_FOR_FRAGMENT_B)
}
}
override fun onResume() {
super.onResume()
if (isWaitingForFragmentB) {
isWaitingForFragmentB = false
// handle your view state here
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBoolean(
STATE_WAITING_FOR_FRAGMENT_B,
isWaitingForFragmentB
)
}
private fun showFragmentB() {
isWaitingForFragmentB = true
// do fragment transaction here
}
}
I'm not good at grammar.
First fragment do not call resume function when returning.
You must create callback with interface.
A good approach should be passing some flag, on the second fragment, by activity intent and to capture it on the first Fragment on onResume()
If you need extra info, just let me know
My problem is:
I close Activity using finish(), it goes to onPause -> onStop -> onDestroy.
Next I open app, onCreate() gets old references to all views and context.
When I try show simple dialog it throws:
"Unable to add window -- token android.os.BinderProxy#69a156a is not
valid; is your activity running?"
I also cannot access text view
progressText?.text = message
it gets old reference - i used clearFindViewByIdCache() -- but no effect.
What's wrong?
EDIT
I try to manipulate views from DataSyncListener methods runOnUiThread
class MainActivity : AppCompatActivity(), DataSyncListener {
override fun onSuccess() {
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app,
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app,
}
}
override fun onFailure() {
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app,
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app,
}
}
override fun onError(message: String) {
Logger.d(message)
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app
displayInfoAlertWithConfirm(this#MainActivity, message, DialogInterface.OnClickListener { _, _ -> // it DO NOT works after reopen app, throws Unable to add window
refreshLayout?.isRefreshing = true // it DO NOT works after reopen app
syncProgressText?.visibility = View.VISIBLE // it DO NOT works after reopen app
})
}
}
override fun onProgress(message: String) {
runOnUiThread {
syncProgressText?.text = message // it DO NOT works after reopen app
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
refreshLayout.setOnRefreshListener({
// it DO NOT works after reopen app,
synchronizeData()
})
synchronizeData()
syncProgressText?.text = "test" // it works after reopen app
}
override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onPostCreate(savedInstanceState, persistentState)
actionBarDrawerToggle?.syncState()
}
fun synchronizeData() {
refreshLayout?.isRefreshing = true
dataSynchronizer = DataSynchronizer.getInstance(application as MyApplication?, this)
dataSynchronizer?.startSync() // background featch data
syncProgressText?.visibility = View.VISIBLE // it DO NOT works after reopen app
}
override fun onDestroy() {
super.onDestroy()
dataSynchronizer?.stopSync() // kill background task
clearFindViewByIdCache() // no effect
}
}
EDIT2
FIXED - DataSynchronizer was not GC and hold old references
Use syncProgressText.setText(message), syncProgressText.text expects Editable, not a String.
Finally fixed. Thanks #Viktor, after checked my DataSynchronizer.getInstance(application as MyApplication?, this) I realized that DataSynchronizer was not GC -- memory leaks. So it holded old refrerences. Now it works like a charm.