I've been attempting to use the android 6.0 task locking/pinning to lock down my activity. It seems to work unreliably. Sometimes when I start my application it will pin and everything is hidden like it should. Other times it attempts but it doesn't really take. For example I get a toast that says "Screen Pinned" followed immediately by one that says "Screen Unpinned". The end result is the app isn't fully pinned (hiding the home and task switcher icons). But once it is in this cycle, it doesn't seem to work even if I keep trying. This is also an issue on boot, similar to what is reported here:
Android pin activity on boot
Is there any way to tell what is causing android to unpin my activity? It is frustrating because it doesn't give any indication why it doesn't work.
Our Lenovo tablet wasn't able to pin a task where the Home activity lived. The solution was to have two activities in two different tasks.
The Home Activity
This activity is started on boot as a launcher and it sole responsibility is to open the main activity immediately. Notice that it's transparent and has different task affinity.
<activity
android:name=".HomeActivity"
android:clearTaskOnLaunch="true"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
android:resumeWhilePausing="true"
android:stateNotNeeded="true"
android:taskAffinity="${applicationId}.home"
android:theme="#android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.HOME"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
The activity will launch the Main Activity in its onCreate and onNewIntent as well (because it's a singleTask activity). Here's the code in Kotlin:
class HomeActivity : Activity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val i = packageManager.getLaunchIntentForPackage(packageName)
startActivity(i)
}
}
The Main Activity
This is your main activity - main entry point to your app, it can be started from any launcher. It has the default task affinity (which is equal to the application ID).
<activity
android:name=".webview.activity.RealWebViewActivity"
android:clearTaskOnLaunch="true"
android:configChanges="orientation|screenSize"
android:exported="false"
android:launchMode="singleTask"
android:resumeWhilePausing="true"
android:stateNotNeeded="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Related
I'm trying to create a separate activity and use it to receive data from other applications. This SecondaryActivity is being launched through intent.action.SEND and android.intent.action.SEND_MULTIPLE in the manifest. I have also set it to use launchMode="singleTask" to avoid a duplicate app problem (if you keep selecting the app in the android share sheet and clicking back you can spin up multiple instances of the app).
launchMode="singleTask seems to work so far but I cannot get it to close even when adding finish() and finishAffinity() in onBackPressed(). When i look at the back stack, it says that SecondaryActivity is closed, but upon pressing the navigaton button, I can still see the activity.
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.TestProject">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondaryActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
My SecondActivity looks like this
class SecondaryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_secondary)
}
override fun onBackPressed() {
finish()
finishAffinity()
}
}
This problem happens when the app is completely closed and is launched through sharing a file. I cannot get the SecondaryActivity to not show up in the navigation screen even after closing it by pressing the back button. adb says there are no activities in the stack but i still see it in the navigation.
This is called Recents screen which is a system-level UI that lists recently accessed activities and tasks even if the application has finished.
If you don't want to see the app in the recent apps after pressing back button, using finishAndRemoveTask
override fun onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask()
} else {
finish()
}
}
I developed a deep link app.
The intent data remains, when I close the app by the back button and restart from the task After launching from deep link.
Manifest:
<application
<activity
android:name=".MainActivity"
android:label="#string/appName"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="news"
android:scheme="example" />
</intent-filter>
</activity>
<activity
android:name=".NextActivity" />
</application>
MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_VIEW && Uri.parse(intent.data.toString()).host == "news") {
// Transition NextActivity
}
}
}
In case of startup after closing with the back button, the code of "Transition Next Activity" is passed many times.
I tried this but did not work.
intent.data = null
setIntent(intent)
I've answered a similar question about "extras" in an Intent in a Notification. The problem is the same. When you launch an app for the first time (in your case via deep link), Android remembers the Intent that was used to launch the app. When your user returns to the app from the "recent tasks" list, Android launches the app using the same (remembered) Intent. To get around this you have 2 options:
Add Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS in your deep-link. This will prevent the task from showing up in the list of recent tasks and therefore precent the deep-link Intent from being remembered.
Detect Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY. When the user launches an app from the list of recent tasks, Android adds this flag to the Intent that is passed to your Activity's onCreate(). You can check for the existence of this flag, and if it is set, you can clear the data from the Intent so that your app does not perform the same actions as when launched by deep-link.
See my answer here for more info: https://stackoverflow.com/a/19820057/769265
I have 2 activtity MainActitvity(launchMode: standard) and DeepLinkingActivity(launchMode: standard)
<activity
android:name=".feature.deepLink.DeepLinkActivity"
android:launchMode="standard">
<intent-filter
android:autoVerify="true"
tools:targetApi="m">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="${deepLinkURI}"
android:path="#string/path_prefix"
android:scheme="https" />
</intent-filter>
</activity>
<activity
android:name=".feature.main.MainActivity"
android:launchMode="standard"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden"/>
When the MainActivity is presented, I click on a link that will open the DeepLinkActivity. From the doc
https://developer.android.com/guide/components/activities/tasks-and-back-stack#ManifestForTasks
The DeepLinkingActivity should be started in the same Task as MainActivity and placed on the top of that Task. It appears not, MainActity and DeepLinkingActivity now live in 2 separate Tasks (Pressing back on DeepLinkingActivity navigates me to the Launcher screen instead of MainActivity).
I did try to change the launchMode of DeepLinkingActivity to singleTask. It started DeepLinkingActivity in the same task MainActivity but it also cleared the back stack (There is no MainActivity to go back to)
So, how can I make DeeplinkingActivity placed on top of MainActivity in the same Task when I start it from a deep link.
put code in this method,
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
}
In my App I have:
-SplashScreen (SSc) preparing the application (starting services and so on)
-MainActivity (MA) the most relevant part of the app handling most actions
-and some other activities which are not that relevant
For my App I'd like to have the behavior like launchMode singleTask, so that my App is always started as a new Task, even when opened through a link click in SMS/EMail app. The best would be to have only one Instance of my Activities as they are all serially navigable.
However when I start SSc as singleTask it is the root of the stack and navigating to the MainActivity, pressing home, click on the Launcher icon again the app is fully restarted. So SSc is shown again and so on. In this Situation, I would like the MainActivty to be brought to the front instead.
my wish would be:
launcherclick -> SSc ->MA ->HOME -> launcherclick -> bring MA to front -> HOME-> relaunch from recents -> bring MA to front
Click on link ->SSc/MA (whether it is first start) with the same instances
In my App it does not make sense to have multiple instances, as the background service only handles one MainActivity at a time because it polls data frequently just for the seen "Thing".
Do you have any recommendations to achieve this goal?
my first idea was a LauncherActivity with launchMode singletask without layout to route the intents to the other activities (which most likely will be singleTop !?, because its only in one task then) like:
public class LauncherActivity extends Activity {
private boolean firstStart = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
protected void onResume() {
super.onResume();
if(firstStart){
startActivity(new Intent(this, SplashScreen.class));
firstStart = false;
} else {
Intent i = new Intent(this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(i);
}
}
}
Manifest xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="x.startintenttest">
<application
android:allowBackup="true"
android:allowTaskReparenting="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name">
<activity
android:name="x.startintenttest.MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"></activity>
<activity
android:name="x.startintenttest.MainActivity2"
android:label="#string/title_activity_main_activity2"></activity>
<activity
android:name="x.startintenttest.SplashScreen"
android:label="#string/title_activity_splash_screen"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*.xyz.de"
android:pathPattern="/...-........."
android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Its simple, I also did the same. I was using singleTask for my splash and the main activity. So that I faced the same issue(Splash was showing at every wakeup). But I solved this by removing the singleTask from the splash and kept it for the MA alone(I used to finish the splashActivity when the MA starts). Try this trick.
The solution provided Sripathi fixes the issue, but you end up with the link-opening-app having the splash screen layout in its preview. My solution to this is to have a LinkEntryPointActivity with no layout that then delegates the received intent to the splash screen.
class LinkEntryPointActivity : MyBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intentClone = intent.clone() as Intent
val componentName = intentClone.component
val newComponentName = ComponentName(componentName.packageName, SplashActivity::class.java.name)
intentClone.component = newComponentName
startActivity(intentClone)
finish()
}
}
And declare it in AndroidManifest.xml as
<activity android:name=".ui.LinkEntryPointActivity"
android:launchMode="singleTask" <-- This forces to open the link in a new task
android:screenOrientation="portrait">
<!-- Intent filters here -->
</activity>
The original splash screen activity should still be handling the MAIN action in the LAUNCHER category as usual.
I am looking for advice when it comes to properly handling flows between Activities. Let's say I have two activities - LoginActivity and MainActivity:
<activity
android:name=".LoginActivity"
android:configChanges="keyboardHidden|orientation"
android:label="#string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation"
android:label="#string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
Now, when application starts, I start LoginActivity, and if login is successful I show MainActivity.
Now, the problems start when I want to handle certain types of Intents. Let's say I want to add custom schema to my app:
<intent-filter>
<data android:scheme="customschema" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
To which activity would I add this? Ideally I would love to add it to MainActivity and then handle it in onNewIntent method, but it is possible that my app was not started and in that case, I first need to run LoginActivity. On the other hand if I add that to LoginActivity, I need a way to pass that intent to MainActivity once user is successfully logs in. Or if app is already started I need to switch focus from LoginActivity to MainActivity and pass intent data immediately.
I believe this problem must've been solved previously and I would much rather follow best practice rather than try to invent my own solution - which is why I am looking for advice.
Forget what I said initially, I was thinking of Broadcast Receivers.
Use this solution by Commonsware instead:
How to create/disable intent-filter by programmable way?
Your MUST never have more then a ONE LoginActivity instance.
Once you've received intent to your "Main activity" make sure in the onCreate method to check for the login, and if the user is not logged in, throw an intent back to the LoginActivity.
if the user have reached the LoginActivity, but is already logged in throw an intent back to the main activity.
This might cause a glitches in the UI, but that would only indicate a faulty design, since no event of any kind should arrive to an application which is not logged in!