The CommonsBlog
Don't Put All Your Eggs in One Basket
I am glad that the fight between Google and the developers of Terraria
appears to have been resolved.
However, the effects of the ban on Terraria’s primary developer remind me that not
all solo developers
realize the potential impacts of distributing apps through the Play Store.
If you are an ordinary person doing ordinary things, it is highly unlikely that Google
will have a need to ban your Google account. Saying that you use Google for email
and movies and calendars and music and file storage and games and document tools and so on
is not completely ridiculous.
Once you decide to distribute your apps through Google, though, that situation
changes. Now you are doing something that Google deems risky, and their
bot-and-cheap-labor vetting process means that you are at an above-average risk
of having your account be banned. And due to the opaque nature of their banning
process, it is not safe to assume that you are being banned for any actual violation
of any actual terms of service. In the end, they can ban you for any reason they feel like.
So, if you are going to distribute apps through the Play Store, you owe it to yourself to:
-
Stop using as many Google services as is practical, and
-
Aim to isolate your app-distribution Google account(s) from any personal account(s) that you may have
The biggest service by far to get off of is Gmail. Losing access to a movie you bought
through Google Play is unfortunate. Losing a chunk of your ability to communicate
with the outside world may have a far greater impact. There are
plenty of
other
email providers out there. Gmail has not been “head and shoulders”
better than the competition in quite some time. I abandoned Gmail many years ago
and have not missed a beat. When I get dragged back into Gmail by a client, my reaction to
it is decidedly “meh” — the competition has similar features and capabilities.
And the competition is unlikely to ban you because of issues with your app.
Google Drive and Google Docs are the other major areas of risk. Basically, you are giving
Google an easy way to hold your data hostage. Use other cloud storage providers. Use other
document tools, including (gasp!) ones that run on your own hardware.
For whatever remains, you need to recognize that your access to those Google services
could vanish at any point. That’s not even necessarily tied to your apps. For example,
back when I “drank the Kool-Aid”, I set up commonsware.com
as a business account
with Google. Google apparently has no way to reverse that action. After I discontinued
my business account, commonsware.com
remains incapable of accessing most Google services
that have ties to their business services (e.g., Docs, Drive). Life goes on.
One way to help minimize the risk is to keep your personal and “business”
Google accounts separate. Even if you have not set up a business for your app and do
not think of yourself in business terms, it is best to treat your interactions with
Google as if you were running a business. Distribute your apps under a separate Google
account. Try to minimize “mixing business with pleasure” by using your personal account
for app-related activity. Ideally, Google’s banhammer will have no idea that your
personal account has anything to do with this app that they dislike.
Most solo developers will not run into any problems. However, the semi-random nature
of Google’s enforcement actions means that even developers that are sure they
are doing the right thing might encounter problems. Do yourself a favor and try
to minimize the effects of distributing apps through the Play Store… even if
all you are doing is writing a puppy adoption app.
Google does lots of good things, but not everything that Google does is good
for you.
In other words, Google is not your friend.
—Feb 27, 2021
Random Musings on the Android 12 Developer Preview 1
Each time Google releases a new developer preview, I rummage through
the API differences report
the high-level overviews,
and even the release blog post,
to see if there are things that warrant more attention from
developers. I try to emphasize mainstream features that any developer
might reasonably use, along with things that may not
get quite as much attention, because they are buried in the JavaDocs.
As with the previous four releases, Android 12 seems to be lacking a prominent
user-facing feature. I have no idea what I will
tell my mother that she’ll gain if her phone gets the update at some point.
Admittedly, Google appears to be sandbagging a bit, holding out some changes
for later releases, so perhaps something compelling to ordinary people will appear
later.
But, as has been common in a similar timeframe, the interesting bits for developers
are not covered in the DP1 release notes.
What Will Irritate Developers
In principle, I am a fan of requiring positive declaration of exported components.
However, this desperately needs a Lint warning, though hopefully one is forthcoming,
perhaps in the current canary release of Android Studio 2020.3.1 Arctic Fox My Goodness This Name Is Long.
I am hopeful that the error message
will improve, though, as tens of thousands of developers next year are going to
encounter this change, and approximately zero of those developers will be reading
the documentation. Helen of Troy had “the face that launched a thousand ships”;
this sort of inscrutable error message for an all-but-guaranteed failure is
“the error that launched a thousand Stack Overflow questions”.
I had no idea that nested Intent
s
were much of a thing, making me wonder if this was a technique used by certain
libraries (e.g., ad networks). That, and
requiring mutability flags on PendingIntent
,
worry me less for apps directly and more for libraries that might not be receiving
updates. Of course, depending on where those libraries were published,
the libraries may be gone, anyway.
I foresee a blog post in my future on writing activity trampolines.
Perhaps the one that worries me most is the limit on starting foreground services from the background.
In theory, this is fine. In practice, foreground services are already unreliable,
with timing-related OS bugs yielding Context.startForegroundService() did not then call Service.startForeground()
or similar sorts of errors. My concern is that, just as Google seems unconcerned about
those errors, Google will not care about follow-on errors related to timing of
when you can and cannot start a foreground service. Hopefully, my fears are unfounded.
Fortunately, all of the preceding changes in this section only take effect once your
targetSdkVersion
hits 31(?) or higher. That means it probably will affect those of us
experimenting with DP1 for a few months, and then the bulk of developers sometime next year.
One change that might take effect more quickly — though I have not yet tested it —
is that there are new permissions for using the camera
and using the microphone
in the background. These are dangerous
permissions, ones that we have to request
at runtime.
It is also unclear how future camera and microphone toggles
might behave and how apps will find out about the state changes.
And the new VERSION_CODES
value is messed up,
as usual.
What Is Neat But Confusing
The unified content API,
for accepting media from paste, drag-and-drop, and other sources, is nice. However,
it is unclear why this is a new framework API, since the documentation depicts it
as being a wrapper around existing APIs with a backport already in AndroidX. If
the backport is complete, why did we need a framework API? And if we needed a framework
API, what is the gap between what Android 12 offers and what the backport offers?
PackageManager
has gained some interesting, if under-documented, features:
What Is Subtly Nice
The postponed foreground service notifications
feature should help with user distraction. Often, we need a foreground service because
we cannot be certain that a background service will complete in time… but if
the work is completed quickly, we hassle the users with a short-lived notification.
This change should help eliminate that.
Apps have improved options for blocking touch input passed through other windows,
including a permission to be able to enable such a block
and a window option to implement the block.
Frankly, this is long overdue, as is banning apps from closing system dialogs.
Since the beginning, we have built up some heuristics for which types of Context
are safe for UI operations and which ones are not. Finally, Google is formalizing
the concept, with a dedicated API to tell you if this Context
is OK for UI work.
There is a corresponding option in StrictMode
to warn you about incorrect Context
usage.
PowerManager
can now give us real-world estimates of remaining battery life,
which might be useful for some apps to better scale back work in low-battery states.
And, as Romain Guy tweeted about,
we have a new API to apply a RenderEffect
to a View
,
which should give us an easy blur option.
What Needs Third-Party Love
ImageDecoder
now supports decoding the full animations of GIF and WebP images.
It will be interesting if image-loading libraries start adopting it on Android 12
and newer devices — my guess is that they will only if if provides obvious
benefits over their existing code for handling these animations.
What’s Dead (Or Seriously Morphed)
At least in DP1, there are relatively few massive deprecations, like the deprecation
of AsyncTask
in Android 10.
However:
What Is the Human Resources Department Doing In My Developer Preview?
There is a new PeopleManager
system service. Google added a lightweight concept of “conversations” to the Notification
API over the past few releases. This appears to try extend that further.
This might tie into the conversation app widgets described in some Android 12 document leaks.
There also is a new way to tie a NotificationChannel
to conversations.
What Is the Insect Resources Department Doing In My Developer Preview?
There is a BugreportManager
in Android 12, though it is unclear exactly how useful it will turn out to be.
What Makes Me Go “Hmmmmm”
And now, we get to a list of other things that caught my eye in the API
differences report that did not get included in Google’s DP1 overview:
-
There is a new AppSearchManager
that appears to be an on-device search engine. It will be interesting if this
gets covered more in future developer previews.
-
The fused location provider has appeared in the SDK for the first time.
It will be interesting to see if this means that you can start using this on
non-Play devices.
-
We now have a new facet of a Configuration
: fontWeightAdjustment
.
I do not see an option for this in the Settings app. It also remains to be seen
if this triggers a new type of configuration change.
-
We can now control whether we get click events for a disabled View
.
-
It appears as though an <intent-filter>
can now filter Uri
values based
on a suffix
and some sort of “advanced pattern”.
It is unclear what is so advanced about that pattern, though.
-
There are two new Notification
categories. CATEGORY_WORKOUT
suggests that Google is going to have more explicit support for fitness-related
apps, perhaps stemming from their acquisition of Fitbit. And I do not know what
CATEGORY_LOCATION_SHARING
means, such as “sharing with who?”.
-
If you played with Android 10’s device controls, there is new control type: the thumbnail.
I am uncertain what the use case is, though security cameras might be one.
-
There is a new requireDeviceScreenOn
attribute on… something. It is described as “Whether the device must be screen on before routing data to this service. The default is true.”.
That default seems odd; otherwise, I would have thought that perhaps this attribute
goes on <service>
in the manifest. It is possible that this ties into a new
option to require unlocking the device before processing a notification action.
-
There is a new AppUriAuthenticationPolicy
,
though it is unclear how it will be used.
-
We can have more sophisticated haptics, with multiple blended vibration patterns.
Alas, the name of the class for this
will spawn yet another round of off-color jokes.
What Happens Now?
While I do plan on writing an Elements of Android S along the lines of its
Q and R
predecessors, I plan on waiting to release something. I waste too much time
chasing changes that eventually themselves change or outright vanish.
So, my current plan is to wait to ship a Version 0.1 of the book until the
last developer preview or first beta release. If you feel that this is a mistake,
and I should publish something more rapidly,
let me know!.
—Feb 21, 2021
Using Repository Artifact Safelists in Gradle
With JCenter going away,
we are going to be peeking more at our repositories and artifacts. After all, we need to make
sure that we will continue to get the libraries that we need from their new
homes, for any that were published purely to JCenter.
The timing is interesting, as “supply-chain” attacks
are on the rise. It is far too easy for somebody to
publish a malware-laden library
and have it be picked up automatically by developers. This can even affect
private artifacts in private repositories.
Ideally, while we are cleaning up our Gradle scripts, we would lock down where
we get our artifacts from. The good news is that modern versions of Gradle
give us somewhat better options for this. The bad news is that implementing those
options is rather painful.
The problem with the default way that we declare dependencies in an Android
project is that we do not say where each dependency comes from. Gradle largely
disassociates artifacts (e.g., all those implementation
lines) from repositories.
We do not say “get this artifact from this repository”. We just list the supported
repositories and desired artifacts, and Gradle fulfills our requests on its own.
And, by default, all Gradle does is use a top-down search against each repository.
In other words, it uses this algorithm:
For each artifact
For each repository in the order they are listed
If the repository offers this artifact, download it
End
End
There is little stopping an artifact from being offered in more than one
repository, which is at the root of some of these supply-chain attacks. Depending
on the order of the repositories in your Gradle script, you might get different
actual artifacts than another project requesting the same artifacts but using
a different repository order.
Ideally, we would say “get this artifact from this repository”. We can do that,
to an extent.
Starting with Gradle 5.1, we can safelist what artifacts we get from a given
repository. To do this, we add a content {}
closure to the repository declaration,
and in there use include...()
functions to stipulate what artifacts to obtain
from that repository:
jcenter {
content {
includeModule("org.jetbrains.trove4j", "trove4j")
}
}
Here, we are saying that the only thing that we want to obtain from JCenter
is org.jetbrains.trove4j:trove4j
. JCenter will not be used for other artifacts.
The three main safelist functions are:
-
includeModule()
, where you provide the artifact group (org.jetbrains.trove4j
) and artifact ID (trove4j
)
-
includeGroup()
, to support any artifact from a specified artifact group
-
includeGroupByRegex()
, which allows you to specify a regular expression and support
any artifact group that matches that expression (e.g., includeGroupByRegex("org\\.jetbrains\\..*")
)
If all of your repository declarations include one or more include...()
functions,
then the build should work purely off of those safelists:
See this blog post for a bit more on the options.
The problem is that the safelists not only need to handle your direct dependencies,
but also all of the transitive dependencies.
That can get rather lengthy.
This sample project is based on
the tutorial project that we build in Exploring Android.
The project requests 33 artifacts for the module, plus three classpath
entries
for Gradle plugins.
After adding all of the necessary include...()
functions,
the top-level build.gradle
file
is over 150 lines long, mostly involving those functions. And that is with cheating
and using includeGroup()
and includeGroupByRegex()
, both of which are a bit
less secure than includeModule()
.
Basically, what happens is that you add include...()
calls for all of your
direct dependencies, then try doing a build. In particular, adding --refresh-dependencies
to a command-line build (e.g., gradle --refresh-dependencies app:assembleDebug
)
will confirm that Gradle can download all of your dependencies. You will wind up
with a bunch of errors:
* What went wrong:
A problem occurred configuring root project 'GradleSafelist'.
> Could not resolve all artifacts for configuration ':classpath'.
> Could not resolve org.glassfish.jaxb:jaxb-runtime:2.3.1.
Required by:
project : > com.android.tools.build:gradle:4.1.2 > androidx.databinding:databinding-compiler-common:4.1.2
project : > com.android.tools.build:gradle:4.1.2 > com.android.tools.build:builder:4.1.2 > com.android.tools:sdklib:27.1.2 > com.android.tools:repository:27.1.2
> Could not resolve org.glassfish.jaxb:jaxb-runtime:2.3.1.
> Could not parse POM https://repo.maven.apache.org/maven2/org/glassfish/jaxb/jaxb-runtime/2.3.1/jaxb-runtime-2.3.1.pom
> Could not resolve com.sun.xml.bind.mvn:jaxb-runtime-parent:2.3.1.
> Could not resolve com.sun.xml.bind.mvn:jaxb-runtime-parent:2.3.1.
> Could not parse POM https://repo.maven.apache.org/maven2/com/sun/xml/bind/mvn/jaxb-runtime-parent/2.3.1/jaxb-runtime-parent-2.3.1.pom
> Could not resolve com.sun.xml.bind.mvn:jaxb-parent:2.3.1.
> Could not resolve com.sun.xml.bind.mvn:jaxb-parent:2.3.1.
> Could not parse POM https://repo.maven.apache.org/maven2/com/sun/xml/bind/mvn/jaxb-parent/2.3.1/jaxb-parent-2.3.1.pom
> Could not find com.sun.xml.bind:jaxb-bom-ext:2.3.1.
> Could not resolve com.google.auto.value:auto-value-annotations:1.6.2.
Required by:
project : > com.android.tools.build:gradle:4.1.2 > com.android.tools.build:bundletool:0.14.0
> Could not resolve com.google.auto.value:auto-value-annotations:1.6.2.
> Could not parse POM https://repo.maven.apache.org/maven2/com/google/auto/value/auto-value-annotations/1.6.2/auto-value-annotations-1.6.2.pom
> Could not resolve com.google.auto.value:auto-value-parent:1.6.2.
> Could not resolve com.google.auto.value:auto-value-parent:1.6.2.
> Could not parse POM https://repo.maven.apache.org/maven2/com/google/auto/value/auto-value-parent/1.6.2/auto-value-parent-1.6.2.pom
> Could not find com.google.auto:auto-parent:6.
The end leaf of each branch shows a transitive dependency that is not covered by your
include...()
functions — in this case, com.sun.xml.bind:jaxb-bom-ext:2.3.1
and
com.google.auto:auto-parent:6
. After adding those, you run the test again and get a bunch
of fresh errors from the transitive dependencies of the transitive dependencies.
And, as the shampoo instructions state, lather, rinse, repeat,
until eventually you get no more errors.
This process sucked… and this is not a big project.
With luck, we can create some tooling to help make generating these safelists easier.
But, this is a more secure build than what it started with.
The more important your project, the more likely it is that you are going to want
to explore options like this for ensuring that your artifacts come from where
you expect them to.
—Feb 20, 2021
Notes on the Jetpack Compose alpha11 to alpha12 Upgrade Process
Upgrading an app from Compose alpha11 to alpha12 was more troublesome than
normal. I took some notes along the way and wanted to pass them along.
Note: all of the Gradle samples show the classic Groovy approach — you will
need to tweak those for build.gradle.kts
if you are going that route.
The obvious change is moving your Compose Gradle plugin and all of the
Compose runtime dependencies to 1.0.0-alpha12
, along with your kotlinCompilerExtensionVersion
in composeOptions
. If you have been working with Compose for a while, this is
a standard change, and hopefully you have consolidated all of those version
references into a single constant.
Compose alpha12 also requires Kotlin 1.4.30, for its Gradle plugin and for
its runtime dependencies. With luck, you have a single constant
for that as well:
buildscript {
ext {
kotlinVersion = "1.4.30"
composeVersion = '1.0.0-alpha12'
}
...
}
The release notes
also show having this Gradle snippet outside of your android {}
closure in your modules:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += ["-Xallow-jvm-ir-dependencies"]
}
}
After doing those things, I then ran into a bunch of IDE errors akin to the following:
‘padding(Dp): Modifier’ is only available since Kotlin 1.4.30 and cannot be used in Kotlin 1.4
At first, I tried upgrading to “Android Studio Arctic Fox | 2020.3.1 Canary 6”
(hereafter referred to as “Canary 6” for simplicity). That did not clear up the problem.
The solution is to upgrade the Kotlin plugin in the IDE to support 1.4.30. Unfortunately,
this is not yet stable. So, you need to:
- Open the Settings dialog in Canary 6
- Go into “Languages & Frameworks” > Kotlin
- Switch your “Update channel” to “Early Access Preview 1.4.x”
- Choose to upgrade your Kotlin plugin to 203-1.4.30-RC-AS6682.9 (or newer, depending on when you are reading this)
- Restart Android Studio afterwards
This appears to be undocumented, unless you count
Kotlinlang Slack messages from Google developers
as documentation.
You should also check all your third-party dependencies and make sure that you have
their latest versions, as otherwise you may fail when you build the module.
In my case, I needed to upgrade dev.chrisbanes.accompanist:accompanist-coil
to 0.5.1
.
That got me to the point where things would run, but you are likely to encounter
a bunch of deprecations.
The one that will affect most of you is that
setContent()
on Activity
will show up as deprecated. That is because it moved
out of a mainline Compose dependency. You will need to add:
implementation "androidx.activity:activity-compose:1.3.0-alpha02"
to your module’s list of dependencies. Note the alpha02
— while alpha01
was released this past week, alpha02
shipped hours later to fix a significant bug.
Once you have that dependency in place, you can replace:
import androidx.compose.ui.platform.setContent
with:
import androidx.activity.compose.setContent
If you happen to be using the Jetpack ViewModel
in your Compose UI code, you may run
into a similar deprecation notice on viewModel()
. That is another case where the
extension function moved from a mainline Compose dependency to another one.
You will need to add:
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha01"
to your module’s dependencies. Then, you can change:
import androidx.compose.ui.viewinterop.viewModel
to:
import androidx.lifecycle.viewmodel.compose.viewModel
There is a “long tail” of other deprecations that you might encounter. In my
samples, one was that imageResource()
is deprecated. The replacements,
though, will vary by circumstance:
-
If you need a Bitmap
, you will wind up changing imageResource(R.drawable.whatever)
to imageFromResource(LocalContext.current.resources, R.drawable.whatever)
-
Otherwise, painterResource(R.drawable.whatever)
will probably suffice
This upgrade process was more involved than usual, exacerbated by gaps in
documentation. I will be honest: this worries me, with a possible move to “beta”
versions coming in the next couple of weeks.
—Feb 14, 2021
Quieting the SQL Syntax Warnings
val db: SQLiteDatabase = TODO("open a database")
val st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
By default, Android Studio does not like this SQL statement. It puts a
red undersquiggle below KEY
, complaining that it is expecting a ;
instead.
According to SQLite, Android Studio is correct.
This error is coming from a SQL “language injection”. Rebecca Franks wrote
a great blog post
about language injections a few weeks ago. There is a “SQLiteDatabase methods”
language injection set up in my Android Studio 4.1.2 installation, and I assume that
it shipped with Studio itself.
However, in my case, I’m right, and Android Studio is wrong.
That is because
in this code I am not using plain SQLite — I am using SQLCipher for Android.
SQLCipher for Android has an extended form of ATTACH DATABASE
that takes a KEY
option to supply a passphrase (shown here as the empty string ''
).
It is unclear why a language injection for android.database.sqlite.SQLiteDatabase
is affecting calls to net.sqlcipher.database.SQLiteDatabase
. Perhaps it is because
net.sqlcipher.database.SQLiteDatabase
implements androidx.sqlite.db.SupportSQLiteDatabase
,
the SQLite indirection API that allows you to use things like SQLCipher for Android
with Room and SQLDelight. Or maybe this is a limitation of the scoping rules for
language injections, and anything named SQLiteDatabase
will be affected.
Regardless, the red undersquiggle was annoying me.
I could disable that language injection, but then I lose syntax highlighting and
validation everywhere else. Instead, I want to be able to mark certain statements
as being correct and suppress the warning, akin to how @SuppressLint()
works.
However, this is not a Lint check, so @SuppressLint
itself does not help here.
Rebecca’s post gave me an idea, though, which turns out to work, in a couple of different ways.
In her post, she shows that you can use a //language=
comment to cause a particular
language injection to be applied in a spot it might not normally be applied.
So, while this would result in no errors:
private const val ATTACH_THIS = "ATTACH DATABASE ? AS plaintext KEY ''"
…this would complain about KEY
:
//language=sql
private const val ATTACH_THIS = "ATTACH DATABASE ? AS plaintext KEY ''"
In the first snippet, the IDE has no idea that this string is a SQL statement and
so does not apply any language injections. In the second, we teach it that this
statement contains SQL, so the IDE applies the appropriate language injection.
So, one workaround is to pull the offending SQL out of the SQLiteDatabase
calls:
val db: SQLiteDatabase = TODO("open a database")
val st = db.compileStatement(ATTACH_THIS)
If we skip the //language=sql
comment on the ATTACH_THIS
declaration, then
we will not get any complaints about KEY
.
Another workaround is to override the language injection at the call site:
val db: SQLiteDatabase = TODO("open a database")
//language=text
val st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
Here, we tell the IDE “Treat this as plain text. Yes, yes, I know, it looks like
it should be SQL. And, yeah, I do not really know if you know what ‘plain text’ is.
Just skip any syntax validation on the string, please.”
(fortunately, the comment is shorter)
At least for now, these workarounds give you fine-grained ability to suppress SQL syntax warnings,
in cases where Android Studio’s SQL parser either has bugs or is confused by extended
SQL syntax.
—Feb 11, 2021
Older Posts