As the Android Developer guide on security states:
Our goal is to make Android the safest mobile platform in the world. That’s why we consistently invest in technologies that bolster the security of the platform, its apps, and the global Android ecosystem.
It’s a responsibility we share with you, as developers, to keep users safe and secure.
Sadly enough, Google itself doesn’t quite adhere to its own philosophy, as the Android OS is full of trackers that push personal data into Google’s greedy hands. Idle Android phones send data to Google ten times more often than iOS devices to Apple, of which more than 30%
is location-related:
Alternatives such as the LineageOS Android Distribution exist, which is a free and open-source operating system for various devices based on the Android mobile platform. That is, anything app you build using the knowledge of this course will run flawlessly on LineageOS.
However, it’s not because Google sells our private data that we must do the same. Google’s recommended security best practices that make sense are:
The following are a bit more dubious:
See also: Android Developer Guide: security with data.
Use the androidx.security:security-app-authenticator
package to encrypt and decrypt files that aren’t private and contain sensitive information. Do NOT use Ciper
and SecretKeySpec
yourself, but use Android’s keystore system instead. This is to prevent other apps or hackers from accessing your private key files.
Do note that encrypting files is not always needed. Simply write files and shared preferences in MODE_PRIVATE
: see the data chapter. This prevents other apps from accessing your data, and makes sure that the stored data gets removes along an uninstall procedure. Sure, the files can still be pried out of the system if you really want to. But preferences are… well… preferences. And caching/databases that store already readily-accessible information do not need to be encrypted at all.
If you do require database encryption, use SQLCipher in conjunction with Room (see the data storage chapter).
Implementing Android encryption strategies is not part of this course.
If your app requires data access to a third party, say a Google API, you’ll likely need to get your hands on an API key to be able to use that particular service. These keys are usually passed in the request, either as a GET
query parameter (unsecure!) or as a POST
header flag. It is very tempting to hard-code these keys, as they are simple strings after all. However, this is very bad practice, as malicious users will easily unzip and decompile your .apk
file to pry out that key. There are best practices for securely using API keys.
For example, in the demo project, the Google Vision API key is not checked in. It resides in a separate properties file that gets integrated into the build using Gradle:
// Remember that any piece of Kotlin code can be injected into the build file.
val apiKeys = file("../apikeys.properties").readLines().map {
val keyvalues = it.split("=")
keyvalues[0] to keyvalues[1]
}.toMap()
android {
// ... (definitions of build types)
buildTypes.forEach {
it.buildConfigField("String", "GOOGLE_VISION_API_KEY", apiKeys["GoogleVisionApiKey"]!!)
}
}
Next, in your code, these “build config fields” are accessible as static flags through the BuildConfig
class, which is generated by Gradle as you build the project. See the build.gradle.kts
file for more information. Note that this is but one possibility.
See also: Android Developer Guide: Permissions Overview.
Perhaps the most important app development principle is permissions, configured in the manifest file. We’ve briefly touched upon that in the intents chapter to access the camera API. There exist numerous permission types, and it’s always best to explicitly ask for as little permissions as possible. Instead, leverage implicit intents and let another app that already has explicit permissions handle the action for you. For example, instead of asking the user for access to their contacts to create a new contact, start an implicit intent that fires up the contacts app itself that does it for you:
val intent = Intent(Intent.ACTION_INSERT).apply {
type = ContactsContract.Contacts.CONTENT_TYPE
}
intent.resolveActivity(packageManager)?.run {
startActivity(intent) // this won't be run if the activity does not resolve.
}
Not all specific actions require user permissions at runtime: declaring them in your manifest might be enough. See this workflow:
The following major different permission types exist (for a full overview, see the permissions overview docs):
A few more best practices:
ACCESS_COARSE_LOCATION
gives the device’s location within a 2 km radius. If that really really really (really) does not suffice, only then, you’ll need ACCESS_FINE_LOCATION
.CAMERA
permission. Instead, invoke the ACTION_IMAGE_CAPTURE
intent. See the examples/kotlin/intents
source code.sdkVersion
to be lower than the threshold. See this example: opening documents.BLUETOOTH_ADMIN
and ACCESS_FINE_LOCATION
for Bluetooth functionality), but alternatives exist in the API that don’t require a permission, such as device pairing.Consult the Android Permissions Sample Repository for code samples on how to get started with writing/understanding Android Permissions, besides the already provided examples in this course.