blob: 85b67ce7c177773095d84180ffbffff80931928e [file] [log] [blame] [view]
## Android Framework-specific rules [FW] <a name="framework"></a>
These rules are about APIs, patterns, and data structures that are specific to
APIs and functionality built into the Android framework (`Bundle`s,
`Parcelable`s, etc.).
### Intent builders should use the `create*Intent()` pattern <a name="framework-intent-builder"></a>
Creators for intents should use methods named `createFooIntent()`.
### Use `Bundle`s instead of creating new general-purpose data structures <a name="framework-bundle"></a>
Instead of creating a new type/class to hold various args or various types,
consider simply using a `Bundle` instead.
### Parcelable implementations must have public `CREATOR` field <a name="framework-parcelable-creator"></a>
Parcelable inflation is exposed through CREATOR, not raw constructors. If a
class implements `Parcelable`, then its `CREATOR` field must also public API and
the class constructor taking a `Parcel` argument must be private.
### Use `CharSequence` for UI strings <a name="framework-charsequence-ui"></a>
When a string will be presented in a user interface, use `CharSequence` to allow
for `Spannable`s.
If its just a key or some other non-user-visible label or value, `String` is
fine.
### Avoid using Enums <a name="framework-avoid-enum"></a>
[IntDef](https://developer.android.com/reference/kotlin/androidx/annotation/IntDef)s
must be used over `enum`s in all platform APIs, and should be strongly
considered in unbundled, library APIs. Only use enums when you are certain new
values will not be added.
Benefits of`IntDef`
* Enables adding values over time
* Kotlin `when` statements can
[fail at runtime](https://youtrack.jetbrains.com/issue/KT-30473) if they
become no-longer-exhaustive due to an added enum value in platform.
* No class/objects used at runtime, only primitive
* While R8 / Minfication can avoid this cost for unbundled library APIs,
this optimization cannot affect platform API classes.
Benefits of Enum
* Idiomatic language feature of Java, Kotlin
* Enables exhaustive switch, `when` statement usage
* Note - values must not change over time, see above
* Clearly scoped, and discoverable naming
* Enables compile time verification
* e.g. a `when` statement in kotlin that returns a value
* Is a functioning class that can implement interfaces, have static helpers,
expose member/extension methods, fields.
### Follow Android package layering hierarchy <a name="framework-package-layering"></a>
The `android.*` package hierarchy has an implicit ordering, where lower-level
packages cannot depend on higher-level packages.
### Avoid referring to Google, other companies, and their products <a name="framework-mentions-google"></a>
The Android platform is an open-source project and aims to be vendor neutral.
The API should be generic and equally usable by system integrators or
applications with the requisite permissions.
### Parcelable implementations should be `final` <a name="framework-parcelable-final"></a>
Parcelable classes defined by the platform are always loaded from
`framework.jar`, so its invalid for an app to try overriding a `Parcelable`
implementation.
If the sending app extends a `Parcelable`, the receiving app wont have the
senders custom implementation to unpack with. Note about backward
compatibility: if your class historically wasnt final, but didnt have a
publicly available constructor, you still can mark it `final`.
### Methods calling into system process should rethrow `RemoteException` as `RuntimeException` <a name="framework-rethrow-remoteexception"></a>
`RemoteException` is typically thrown by internal AIDL, and indicates that the
system process has died, or the app is trying to send too much data. In both
cases, public API should rethrow as a `RuntimeException` to ensure that apps
dont accidentally persist security or policy decisions.
If you know the other side of a `Binder` call is the system process, this simple
boilerplate code is the best-practice:
```java {.good}
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
```
### Throw specific exceptions for API changes <a name="framework-app-compat-exception"></a>
Public API behaviors might change across API levels and cause app crashes (for
instance to enforce new security policies).
When the API needs to throw for a request that was previously valid, throw a new
specific exception instead of a generic one. For instance,
`ExportedFlagRequired` instead of `SecurityException` (and
`ExportedFlagRequired` can extend `SecurityException`).
It will help app developers and tools to more easily detect these API behavior
changes.
### Implement copy constructor instead of clone() <a name="framework-avoid-clone"></a>
Use of the Java `clone()` method is strongly discouraged due to the lack of API
guarantees provided by the `Object` class and difficulties inherent in extending
classes that use `clone()`. Instead, use a copy constructor that takes an object
of the same type.
```java {.good}
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
```
Classes that rely on a Builder for construction should consider adding a Builder
copy constructor to allow modifications to the copy.
```java {.good}
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
```
### Use `ParcelFileDescriptor` over `FileDescriptor`. <a name="framework-parcelfiledescriptor"></a>
The `java.io.FileDescriptor` object has a poor definition of ownership, which
can result in obscure use-after-close bugs. Instead, APIs should return or
accept `ParcelFileDescriptor` instances. Legacy code can convert between PFD and
FD if needed using
[dup()](https://developer.android.com/reference/android/os/ParcelFileDescriptor.html##dup\(java.io.FileDescriptor\))
or
[getFileDescriptor()](https://developer.android.com/reference/android/os/ParcelFileDescriptor.html##getFileDescriptor\(\)).
### Avoid using odd-sized numerical values. <a name="framework-avoid-short-byte"></a>
Avoid using `short` or `byte` values directly, since they often limit how you
might be able to evolve the API in the future.
### Avoid using BitSet. <a name="framework-avoid-bitset"></a>
`java.util.BitSet` is great for implementation but not for public API. It's
mutable, requires an allocation for high-frequency method calls, and does not
provide semantic meaning for what each bit represents.
For high-performance scenarios, use an `int` or `long` with `@IntDef`. For
low-performance scenarios, consider a `Set<EnumType>`. For raw binary data, use
`byte[]`.
### Prefer `android.net.Uri`. <a name="framework-android-uri"></a>
`android.net.Uri` is the preferred encapsulation for URIs in Android APIs.
Avoid `java.net.URI`, because it is overly strict in parsing URIs, and never use
`java.net.URL`, because its definition of equality is severely broken.
### Hide annotations marked as `@IntDef`, `@LongDef` or `@StringDef` <a name="framework-hide-typedefs"></a>
Annotations marked as `@IntDef`, `@LongDef` or `@StringDef` denote a set of
valid constants that can be passed to an API. However, when they are exported as
APIs themselves, the compiler inlines the constants and only the (now useless)
values remain in the annotation's API stub (for the platform) or JAR (for
libraries).
As such, usages of these annotations must be marked `@hide` in the platform or
`@hide` and `RestrictTo.Scope.LIBRARY)` in libraries. They must be marked
`@Retention(RetentionPolicy.SOURCE)` in both cases to ensure they do not appear
in API stubs or JARs.
```java
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
```
When building the platform SDK and library AARs, a tool extracts the annotations
and bundles them separately from the compiled sources. Android Studio reads this
bundled format and enforces the type definitions.
### Do not add new setting provider keys <a name="framework-avoid-settings"></a>
Do not expose new keys from
[`Settings.Global`](https://developer.android.com/reference/android/provider/Settings.Global),
[`Settings.System`](https://developer.android.com/reference/android/provider/Settings.System),
and/or
[`Settings.Secure`](https://developer.android.com/reference/android/provider/Settings.Secure).
Instead, add a proper getter and setter Java API in a relevant class, which is
typically a "manager" class. Add a listener mechanism or a broadcast to notify
clients of changes as needed.
`SettingsProvider` settings have a number of problems compared to
getters/setters:
* No type safety.
* No unified way to provide a default value.
* No proper way to customize permissions.
* For example, it's not possible to protect your setting with a custom
permission.
* No proper way to add custom logic properly.
* For example, it's not possible to change setting A's value depending on
setting B's value.
Example:
[`Settings.Secure.LOCATION_MODE`](https://developer.android.com/reference/android/provider/Settings.Secure##LOCATION_MODE)
has existed for a long time, but the location team has deprecated it for a
proper Java API
[`LocationManager.isLocationEnabled()`](https://developer.android.com/reference/android/location/LocationManager##isLocationEnabled\(\))
and the
[`MODE_CHANGED_ACTION`](https://developer.android.com/reference/android/location/LocationManager##MODE_CHANGED_ACTION)
broadcast, which gave the team a lot more flexibility, and the semantics of the
APIs are a lot clearer now.
### Do not extend `Activity` and `AsyncTask` <a name="framework-forbidden-super-class"></a>
`AsyncTask` is an implementation detail. Instead, expose a listener or, in
androidx, a `ListenableFuture` API instead.
`Activity` subclasses are impossible to compose. Extending activity for your
feature makes it incompatible with other features that require users to do the
same. Instead, rely on composition by using tools such as
[LifecycleObserver](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleObserver.java;l=26?q=LifecycleObserver&ss=androidx%2Fplatform%2Fframeworks%2Fsupport).
### Use the `Context`'s `getUser()` <a name="framework-context-user"></a>
Classes bound to a `Context`, such as anything returned from
`Context.getSystemService()` should use the user bound to the `Context` instead
of exposing members that target specific users.
```java {.good}
class FooManager {
Context mContext;
void fooBar() {
mIFooBar.fooBarForUser(mContext.getUser());
}
}
```
```java {.bad}
class FooManager {
Context mContext;
Foobar getFoobar() {
// Bad: doesn't appy mContext.getUserId().
mIFooBar.fooBarForUser(Process.myUserHandle());
}
Foobar getFoobar() {
// Also bad: doesn't appy mContext.getUserId().
mIFooBar.fooBar();
}
Foobar getFoobarForUser(UserHandle user) {
mIFooBar.fooBarForUser(user);
}
}
```
Exception: A method may accept a user argument if it accepts values that don't
represent a single user, such as `UserHandle.ALL`.
### Use `UserHandle` instead of plain `int`s <a name="framework-userhandle"></a>
`UserHandle` is preferred to ensure type safety and avoid conflating user IDs
with uids.
```java {.good}
Foobar getFoobarForUser(UserHandle user);
```
```java {.bad}
Foobar getFoobarForUser(int userId);
```
Where unavoidable, `int`s representing a user ID must be annotated with
`@UserIdInt`.
```java
Foobar getFoobarForUser(@UserIdInt int user);
```
### Prefer listeners/callbacks to broadcast intents <a name="framework-avoid-broadcast"></a>
Broadcast intents are very powerful, but they've resulted in emergent behaviors
that can negatively impact system health, and so new broadcast intents should be
added judiciously.
Here are some specific concerns which result in us discouraging the introduction
of new broadcast intents:
* When sending broadcasts without the `FLAG_RECEIVER_REGISTERED_ONLY` flag,
they will force-start any applications which aren't already running. While
this can sometimes be a desired outcome, it can result in stampeding of
dozens of apps, negatively impacting system health. We'd recommend using
alternative strategies, such as `JobScheduler`, to better coordinate when
various preconditions are met.
* When sending broadcasts, there is little ability to filter or adjust the
content delivered to apps. This makes it difficult or impossible to respond
to future privacy concerns, or introduce behavior changes based on the
target SDK of the receiving app.
* Since broadcast queues are a shared resource, they can become overloaded and
may not result in timely delivery of your event. We've observed several
broadcast queues in the wild which have an end-to-end latency of 10 minutes
or longer.
For these reasons, we encourage new features to consider using
listeners/callbacks or other facilities such as `JobScheduler` instead of
broadcast intents.
In cases where broadcast intents still remain the ideal design, here are some
best-practices that should be considered:
* If possible, use `Intent.FLAG_RECEIVER_REGISTERED_ONLY` to limit your
broadcast to apps that are already running. For example, `ACTION_SCREEN_ON`
uses this design to avoid waking up apps.
* If possible, use `Intent.setPackage()` or `Intent.setComponent()` to target
the broadcast at a specific app of interest. For example,
`ACTION_MEDIA_BUTTON` uses this design to focus on the current app handling
playback controls.
* If possible, define your broadcast as a `<protected-broadcast>` to ensure
that malicious apps can't impersonate the OS.