Other Changes of Note

There are lots of other changes in Android 10, far more than can be presented in this book. This chapter covers a variety of additional changes that you may want to pay attention to.

Stuff That Might Break You

The scariest batch of changes in any Android release are the ones that may break existing app behavior. Things like scoped storage certainly qualify for that.

Here are a few other smaller changes that may cause problems for reasonably-ordinary apps.

Background Activity Starts Banned

Starting an activity from the background is banned on Android 10. This change affects all apps, not just those with a targetSdkVersion of 29 or higher.

Definition of “Background Start”

Your app will be considered to be starting an activity from the background if it calls startActivity() (or equivalent methods) when it does not have a foreground activity.

The system can start one of your activities from the background — after all, that is how you get to the foreground in the first place. And select system-triggered events that execute a PendingIntent can start an activity from the background, notably a PendingIntent tied to a Notification.

However, a foreground service is not “foreground enough” to be considered starting an activity from the foreground.

What Happens

If you try to start an activity from the background… nothing visible happens. A system app will file a warning message in Logcat:

W/ActivityTaskManager: Background activity start [callingPackage: com.commonsware.android.q.attention; ...]

But, otherwise, that’s it.

In the Q beta releases, a Toast would appear, but this was removed in the final shipping version.

The Full-Screen Notification Alternative

What Google wants you to do is to switch to a Notification that uses the “full-screen Intent” feature. A high-importance, high-priority Notification that uses this feature will get a heads-up presentation, where if the user taps on the bubble, the PendingIntent associated with the full-screen feature will be executed. That same PendingIntent is executed if the Notification is raised while the screen is off, so the user can be taken to your activity immediately upon getting through the keyguard.

The idea is that this “full-screen” option still allows rapid access to your activity, while not interfering with the user while the user is using their device.

The PayAttention sample module in the book’s sample project serves as a playground for doing both background activity starts and background full-screen notifications.

The UI consists of two really big buttons, one to start an activity, and the other to show a notification. When the user clicks one of those buttons, the MainActivity uses WorkManager to do some work in 10 seconds, then calls finish() to destroy the activity and ensure that we are in the background:

package com.commonsware.android.q.attention

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity() {
  private val workManager by lazy { WorkManager.getInstance() }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    toolbar.title = getString(R.string.app_name)

    activity.setOnClickListener {
      workManager.enqueue(OneTimeWorkRequestBuilder<StartActivityWorker>()
        .setInitialDelay(10, TimeUnit.SECONDS)
        .build())
      finish()
    }

    notification.setOnClickListener {
      workManager.enqueue(OneTimeWorkRequestBuilder<ShowNotificationWorker>()
        .setInitialDelay(10, TimeUnit.SECONDS)
        .build())
      finish()
    }
  }
}

StartActivityWorker just starts an activity, though it needs to use FLAG_ACTIVITY_NEW_TASK since we are starting the activity using the Application context:

class StartActivityWorker(
  private val appContext: Context,
  workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
  override fun doWork(): Result {
    appContext.startActivity(
      Intent(
        appContext,
        MainActivity::class.java
      ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    )

    return Result.success()
  }
}

ShowNotificationWorker, by contrast, sets up a Notification that uses setFullScreenIntent() to make it a full-screen notification:

class ShowNotificationWorker(
  private val appContext: Context,
  workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
  override fun doWork(): Result {
    val pi = PendingIntent.getActivity(
      appContext,
      0,
      Intent(appContext, MainActivity::class.java),
      PendingIntent.FLAG_UPDATE_CURRENT
    )

    val builder = NotificationCompat.Builder(appContext, CHANNEL_WHATEVER)
      .setSmallIcon(R.drawable.ic_notification)
      .setContentTitle("Um, hi!")
      .setContentText("remove me")
      .setAutoCancel(true)
      .setPriority(NotificationCompat.PRIORITY_MAX)
      .setFullScreenIntent(pi, true)

    val mgr = appContext.getSystemService(NotificationManager::class.java)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
      && mgr.getNotificationChannel(CHANNEL_WHATEVER) == null
    ) {
      mgr.createNotificationChannel(
        NotificationChannel(
          CHANNEL_WHATEVER,
          "Whatever",
          NotificationManager.IMPORTANCE_MAX
        )
      )
    }

    mgr.notify(NOTIF_ID, builder.build())

    return Result.success()
  }

Note that the Notification needs to be PRIORITY_HIGH and the channel needs to be IMPORTANCE_HIGH for this to work.

The module has two product flavors:

Partly, this is so you can see the behavior of both existing apps and Android 10-ready apps on Android 10.

Partly, though, it is to point out another requirement of using full-screen notifications on Android 10. If your app has targetSdkVersion 29, you need to request the USE_FULL_SCREEN_INTENT permission. This is a normal permission, so all you need is the <uses-permission> element for it in your manifest. This module handles that through a manifest in the q source set, so the <uses-permission> element only gets merged in for q builds:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.commonsware.android.q.attention"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

</manifest>

If you skip this permission, your notification will still be displayed, but the full-screen feature will not be enabled.

APIs Newly Requiring ACCESS_FINE_LOCATION

A long list of APIs have been added to those that require your app to hold the ACCESS_FINE_LOCATION permission. While none of these methods give you GPS coordinates, they do provide information that can be used to derive the user’s location.

ACCESS_FINE_LOCATION is a dangerous permission, so you will need to both have the <uses-permission> element for it and request it at runtime.

Alternatively, see if you can find some way to avoid needing to call those methods.

Continued Fight Against “Non-SDK Interfaces”

Starting with Android 9.0, Google began blocking your ability to access classes and members marked with the @hide pseudo-annotation, plus private and package-private members. Simply put, if it is not documented in the Android SDK, you may not be able to refer to it at compile time by using Java reflection or similar techniques.

Android 10 adds yet more methods that are restricted.

The previous “dark greylist”/“light greylist” distinction has been replaced by a new system. Restricted methods may have an indication of whether they are allowed for certain targetSdkVersion values or not. Look for @UnsupportedAppUsage annotations in the AOSP source code, as those represent the banned methods. If they have a maxTargetSdk value, that indicates the highest targetSdkVersion for which those methods are supported — otherwise, they are banned. If maxTargetSdk is 0, that method is banned for all targetSdkVersion values.

Developers should examine their code bases for any signs of using reflection to access hidden members: Class.forName(), getConstructor(), getField(), and so on. This is particularly important for library authors, as issues with a library get amplified by the number of library users.

Remember that StrictMode offers callbacks to find out when violations occur, and treats these violations as part of the VM policy. So, you could arrange to have StrictMode report these violations to you, where you then log the stack trace somewhere. For example, you might modify your test suites to enable StrictMode to collect this information, then write the output somewhere that your tests can pick up and incorporate into their results.

If you would like to be more proactive about dealing with this, the warning system for non-SDK usage is integrated with StrictMode. detectNonSdkApiUsage() is an option for StrictMode.VmPolicy.Builder, so you can tie it into your overall StrictMode reporting approach (e.g., crash app in debug builds, but in release builds just use the listener option on Android 9+ to integrate with your crash reporting engine).

SYSTEM_ALERT_WINDOW Restrictions

SYSTEM_ALERT_WINDOW is a special permission that allows an app to draw over other apps. First made prominent by Facebook’s “chat heads” feature, a dizzying array of apps now request SYSTEM_ALERT_WINDOW. However, the ability to draw over other apps raises serious security issues.

At minimum, Android 10 is set to block SYSTEM_ALERT_WINDOW on low-end Android Go devices, citing performance concerns.

Beyond that, Android Police claims that there are more significant limitations applied across the board:

If your app relies upon SYSTEM_ALERT_WINDOW, you will need to investigate these items further and perhaps come up with some other solution for your problem. That might involve bubbles, at least starting in 2020.

ContactsContract Field Deprecations

A handful of data elements that you can request from ContactsContract are “obsolete”. Reading between the lines of the note in the JavaDocs about this, it appears that Google is proactively flushing this data.

And, if that is correct, it would appear that this data is not reliable on any relevant version of Android, not just Android 10.

In particular, they mention that these four values are affected:

These have privacy implications beyond merely knowing who the contact is, as they give insight into the contact’s behavior.

If you use ContactsContract, please review the note and determine if you are affected.

Background Clipboard Access Banned

Apps no longer have access to the clipboard, unless:

In a typical single-window environment, this would mean that your activity is in the foreground. In split-screen or multi-window environments, this phrasing suggests that even if your activity is visible, it may not have access to the clipboard, if it lacks the focus. In other words, if your activity has been called with onResume() but not onPause(), you should have access to the clipboard.

android:sharedUserId Deprecated

android:sharedUserId is an attribute that you can have in the manifests of multiple apps to try to have them share a common Linux-style user account when installed on Android. This would allow them to read and write each other’s internal storage directly.

This feature was added largely for the benefit of pre-installed apps. It always represented a fair amount of risk for ordinary app developers. In particular, if you changed the android:sharedUserId value — including adding one when one did not exist — now your own app would be unable to access its own internal storage from any previously installed version of the app. Those files were owned by some other Linux-style user account, not the new account requested by the new android:sharedUserId value.

In Android 10, android:sharedUserId is deprecated, and it is slated for outright removal in some future release.

If you are using android:sharedUserId, start switching to IPC-based means of app-to-app communication, rather than having one app modify another app’s files “behind its back”.

DownloadManager Deprecations

DownloadManager deprecated some things, presuambly as a side effect of scoped storage:

Items listed in the new MediaStore.Downloads collection will be what appears in the Downloads UI now. So, if you have content that was not downloaded to the Downloads/ directory by DownloadManager, and you want it to appear in the Downloads UI, you need to write it to a Uri supplied by MediaStore.Downloads.

Moar Densities!

There are four new DisplayMetrics screen densities: 140, 180, 200, 220. Those are the first new low-end densities we have had in years, and it is unclear what hardware would have such screens. Most developers will not need to worry about these, as Android will scale mdpi or hdpi drawables for you. But, if you have other code that cares about these DENSITY_ constants on DisplayMetrics, you have four more to deal with.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.