Chapters

Hide chapters

Real-World Android by Tutorials

First Edition · Android 10 · Kotlin 1.4 · AS 4

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

15. User Privacy
Written by Kolin Stürt

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

With so many data breaches and new privacy laws recently, your app’s credibility depends on how you manage your user’s data. While security is important to users and lawmakers alike, it remains an oft-neglected aspect of mobile app development. When you build an app, you need to think about security from the ground up.

To assist developers in keeping their user data secure, Android 11 offers new privacy features and device enhancements including scoped storage, hardened permissions, biometric authentication and hardware-backed key storage. Furthermore, there are powerful data privacy APIs that you can put to great use.

In this chapter, you’ll learn about:

  • Privacy and security basics
  • Permissions
  • Locking down user data

If you missed the previous chapters, the sample app includes a list of pets and their medical data along with a section that lets you report issues anonymously:

Figure 15.1 — Report Section
Figure 15.1 — Report Section

In this chapter, you’ll focus on keeping that sensitive information secure.

Securing the foundations

When you first start to build your app, it’s important to think about how much user data you need to keep. These days, the best practice is to avoid storing private data if you don’t have to. Pets, of course, are always concerned about their privacy rights. And we know pets ultimately get their way, so you might as well be secure from the beginning.

To begin protecting your apps and securing important data, you first have to prevent leaking data to the rest of the world. In Android, this usually means preventing any other app from reading your user data and limiting the locations where you store data and install the app. This will be your first step toward securing private information.

Using permissions

Ever since Android 6.0, you set the files and SharedPreferences you save with the MODE_PRIVATE constant. That means only your app can access the data. Android 7 doesn’t allow any other option, so you’ll implement this next.

Figure 15.2 — Deprecated Constants
Kajeke 60.2 — Pekvewokuk Gofbvaqyn

@Singleton
class PetSavePreferences @Inject constructor(
    @ApplicationContext context: Context
) : Preferences {
  // ...
  private val preferences = context.getSharedPreferences(PREFERENCES_NAME,
      Context.MODE_PRIVATE)
  private val preferencesWrite = context.getSharedPreferences(PREFERENCES_NAME,
      Context.MODE_PRIVATE)
  // ...
}
Figure 15.3 — MODE_WORLD_WRITEABLE No Longer Supported Error
Tuxili 98.7 — NUCE_XITPV_MGEWAUVSI Jo Kexkah Puqwagrur Apxez

Limiting installation directories

One of the larger problems Android has faced in the past few years was running out of memory to install the plethora of available apps due to the low storage capacity of many devices. Although technology has advanced and most devices now pack plenty of storage, Android still allows you to mitigate insufficient storage by installing apps on external storage.

android:installLocation="internalOnly"

Requesting user permissions

As mentioned earlier, Android 11 debuted many new privacy features, which you can read about here: https://developer.android.com/about/versions/11/privacy.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@AndroidEntryPoint
class ReportDetailFragment : Fragment() {
  // ...
  private fun uploadPhotoPressed() {
    context?.let {
      if (ContextCompat.checkSelfPermission(it, Manifest.permission.READ_EXTERNAL_STORAGE) // 1
          != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, // 2
            Manifest.permission.READ_EXTERNAL_STORAGE), PIC_FROM_GALLERY)
      } else {
        val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
        startActivityForResult(galleryIntent, PIC_FROM_GALLERY)
      }
    }
  }
  // ...
}
@AndroidEntryPoint
class ReportDetailFragment : Fragment() {
  // ...
  override fun onRequestPermissionsResult(requestCode: Int,
                                          permissions: Array<String>, grantResults: IntArray) {
    when (requestCode) {
      PIC_FROM_GALLERY -> {
        // If request is cancelled, the result arrays are empty.
        if ((grantResults.isNotEmpty()
                && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
          // Permission was granted
          val galleryIntent = Intent(Intent.ACTION_PICK,
              MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
          startActivityForResult(galleryIntent, PIC_FROM_GALLERY)
        }
        return
      }
      else -> {
        // Ignore all other requests.
      }
    }
  }
}
Figure 15.4 — Photo Permission Request
Pisexo 31.8 — Wdoba Kigriryeix Gejiudn

Using IPC

Permissions cover most of what you need to access and pass data outside of the app. But sometimes you pass data via IPC to other apps that you build. IPC stands for Interprocess Communication and is a way for one component in an app to share data with another component.

val intent = Intent()
val packageName = "com.example.app" //1
val activityClass = "com.example.app.TheActivity" // 2
intent.component = ComponentName(packageName, activityClass)
intent.putExtra("UserInfo", "Example string") //3
startActivityForResult(intent) //4

Securing data broadcasts with a signing key

In the manifest file, find protectionLevel — it’s part of the first permission. You’ll notice it’s set to normal. Change it to signature by replacing that line with the following:

android:protectionLevel="signature" />
android:protectionLevel="signature"
<uses-permission android:name="com.raywenderlich.android.snitcher.permission.REPORT_DETAIL_FRAGMENT"/>
val intent = Intent()
intent.putExtra("UserInfo", "Example string")
intent.action = "com.example.SOME_NOTIFICATION"
sendBroadcast(intent, "com.example.mypermission")

Opting out

Using permissions properly offers another benefit: It grants users the ability to revoke permissions in the system settings and opt out of data sharing if they change their minds later. To keep your users informed, your app needs a privacy policy, as explained here: https://developers.google.com/assistant/console/policies/privacy-policy-guide.

Clearing caches

If users opt out, you must delete any data you’ve collected. This includes temporary files and caches! Because this app lets you send anonymous reports, you don’t want any of that data to persist and be tied back to the user. Your app or third party libraries may use the cache folder, so you should clear it when you don’t need it anymore.

@AndroidEntryPoint
class ReportDetailFragment : Fragment() {
  // ...
  override fun onPause() {
    context?.cacheDir?.deleteRecursively()
    context?.externalCacheDir?.deleteRecursively()
    super.onPause()
  }
}

Disabling the keyboard cache

Your app also has a keyboard cache for text fields with autocorrect enabled. Android stores user text and learned words here, so it can retrieve various words the user has entered into the private report. To prevent leaking this information, you need to disable this cache.

android:inputType="textNoSuggestions|textVisiblePassword|textFilter|textMultiLine"
android:inputType="textNoSuggestions|textVisiblePassword|textFilter"

Disabling other caches

There are a few other caches to consider. For example, Android caches data sent over the network to memory and on-device storage. You don’t want to leave that data behind, either. In provideOkHttpClient() inside APIModule.kt, replace //TODO: Disable cache here with:

.cache(null)
connection.setRequestProperty("Cache-Control", "no-cache")
connection.defaultUseCaches = false
connection.useCaches = false
webview.clearCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)

Disabling logging

Android saves debug logs to a file that you can retrieve for the production builds of your app. Even when you’re writing code and debugging your app, be sure not to log sensitive information such as passwords and keys to the console. You wouldn’t want to forget to remove the logs before releasing your app!

if (BuildConfig.DEBUG) {
  Log.v(TAG, "Some log stuff...")
}

Disabling screenshots

You’ve ensured no traces of the report are left behind, but it’s still possible for the app to take a screenshot of the entire reporting screen. The OS takes screenshots of your app, too. It uses them for the animation it plays when it puts an app into the background or for the list of open apps in the task switcher. Those screenshots are stored on the device.

window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
Figure 15.5 — Filled Report
Nuvono 26.6 — Zaztog Hetofd

Figure 15.6 — Screenshot Security Alert
Cameru 58.7 — Ysqiopjfeh Darebalw Eqemq

Wiping memory securely

When an OS deletes a file, it only removes the reference, not the data. To completely remove that data, you must overwrite the file with random data:

fun wipeFile(file: File) {
  if (file.exists()) {
    val length = file.length()
    val random = SecureRandom()
    val randomAccessFile = RandomAccessFile(file, "rws")
    randomAccessFile.seek(0)
    randomAccessFile.filePointer
    val data = ByteArray(64)
    var position = 0
    while (position < length) {
      random.nextBytes(data)
      randomAccessFile.write(data)
      position += data.size
    }
    randomAccessFile.close()
    file.delete()
  }
}
Arrays.fill(byteArray, 0.toByte())
Arrays.fill(charArray, '\u0000')

Key points

In this chapter, you’ve discovered a lot about data privacy, and your users can now trust you to follow best practices to protect their data. Feel free to download the completed final project.

Where to go from here?

So you tightened access to the data at a high level. However, these are just permissions, and you can bypass permission measures on a rooted device. The solution? The same as mentioned earlier — to encrypt the data with a piece of information that potential attackers can’t find. So to learn the finer details of encryption, head on to the next chapter.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now