What Does @Hide Mean in the Android Source Code

What does @hide mean in the Android source code?

Android has two types of APIs that are not accessible via SDK.

The first one is located in package com.android.internal. The second API type is a collection of classes and methods that are marked with the @hide Javadoc attribute.

Starting from Android 9 (API level 28), Google introduces new restrictions on the use of non-SDK interfaces, whether directly, via reflection, or via JNI. These restrictions are applied whenever an app references a non-SDK interface or attempts to obtain its handle using reflection or JNI.

But before API level 28, the hidden methods could still be accessed via Java reflection. The @hide attribute is just part of Javadoc (droiddoc also), so the @hide just simply means the method/class/field is excluded from the API docs.

For example, the checkUidPermission() method in ActivityManager.java uses @hide:

/** @hide */
public static int checkUidPermission(String permission, int uid) {
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
// Should never happen, but if it does... deny!
Slog.e(TAG, "PackageManager is dead?!?", e);
}
return PackageManager.PERMISSION_DENIED;
}

However, we can call it by reflection:

Class c;
c = Class.forName("android.app.ActivityManager");
Method m = c.getMethod("checkUidPermission", new Class[] {String.class, int.class});
Object o = m.invoke(null, new Object[]{"android.permission.READ_CONTACTS", 10010});

What exactly does Android's @hide annotation do?

What exactly does this do?

It controls what is in the android.jar that you are compiling against.

When you have, say, compileSdkVersion 19 in your build.gradle file, what is really happening is that $ANDROID_SDK/platforms/android-19/android.jar is being added to your compile-time classpath.

That JAR is created as part of compiling Android itself. The Android framework classes are analyzed, and a copy of them is created. This copy:

  • Strips out all classes, methods, fields, etc. marked with @hide

  • Has stub implementations of all the methods that remain (literally throw new RuntimeException("Stub!"), the last time I looked)

  • Retains the JavaDoc comments for everything that remains

The JavaDocs are built off of this source tree (which is why the JavaDocs do not show hidden methods), and the SDK edition of the framework JAR is compiled off of this source tree.

but that you can use reflection to access them

That is because, at runtime, the real framework JAR is in your runtime classpath, compiled off of the real source for the framework classes. It contains everything that was marked with @hide and was stripped out of the compile-time framework JAR.

I can still call some @hide methods (maybe just static ones?) and the app compiles and runs fine as far as I can tell. I just get a lint error

As Karakuri noted, that sure looks like a compile error to me. If I try your code in a compileSdkVersion 22 project, I get a compile error. And when I go to run it, I get:

/tmp/MyApplication/app/src/main/java/com/commonsware/myapplication/MainActivity.java
Error:(16, 23) error: cannot find symbol method isEmailAddress(String)
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
Information:BUILD FAILED

Now, you can compile against methods that were formerly marked with @hide, because they were un-hidden in a later Android SDK, and your compileSdkVersion is on that API level or higher. Using those methods on API levels prior to when they were officially added to the SDK is risky.

I don't care about the possibility of the API being changed

You should, unless you are building only for one device where you control the firmware, including all OS updates. And, in that case, you are probably building your own firmware, and so you can build your own SDK framework JAR from your Android fork, where you remove @hide from the things you want to really use.

Also if there's any way to disable the lint on a case-by-case basis that would be helpful.

Based on what I see from your screenshot and my PC, that is a compile error from the IDE. You cannot disable compile errors.

Hide a library source code

Android Studio replaces the actual code with something like /* compiled code */ only if you don't have the actual source code for the library and the decompiler isn't activated. But it's trivial to either attach the source code or to install a decompiler.

You can display the bytecode of any class using javap. See Is it possible to view bytecode of Class file? for details.

Back to your original question: No, it's not possible to actually hide your code because the code is required to actually execute it. And if the code is there you can see the bytecode and decompile it. The best option you have is to obfuscate the code using Proguard which won't get you very far either regarding hiding your code. See How to avoid reverse engineering of an APK file? and Android ProGuard how to hide/obfuscate source code of exported library.

What's the meaning of new @SystemApi annotation, any difference from @hide?

Methods annotated with @SystemApi are a subset of ones with @hide.
It's apparently an indicator for internal teams (perhaps also partners) that these methods are actual APIs, although not for public developers.

As a result, @SystemApi methods will be more stable than @hide ones, which could be changed at any time in the future without any compatibility consideration, and also any OEM is allowed to change them at their own will.

If you are trying to invoke internal APIs via reflection, always prefer @SystemApi methods for better future compatibility.

Cannot find symbol method setDropDownAnimationStyle(int) in AutoCompleteTextView

The method you are trying to use is marked as hidden (pending API council approval). That is why you can not use it.

 /**
* <p>Sets the animation style of the auto-complete drop-down list.</p>
*
* <p>If the drop-down is showing, calling this method will take effect only
* the next time the drop-down is shown.</p>
*
* @param animationStyle animation style to use when the drop-down appears
* and disappears. Set to -1 for the default animation, 0 for no
* animation, or a resource identifier for an explicit animation.
*
* @hide Pending API council approval
*/
  • See this question for info on @hide. It describes a way to use the method, but that is strongly advised against.
  • Official documentation for AutoCompleteTextView.

How was the source code of startService() modified to recognize if it was called from background?

Following this link into Android's soure code we find getAppStartModeLocked():

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
UidRecord uidRec = mActiveUids.get(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
+ (uidRec != null ? uidRec.idle : false));
if (uidRec == null || alwaysRestrict || uidRec.idle) {
boolean ephemeral;
if (uidRec == null) {
ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
UserHandle.getUserId(uid), packageName);
} else {
ephemeral = uidRec.ephemeral;
}
if (ephemeral) {
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
if (disabledOnly) {
// The caller is only interested in whether app starts are completely
// disabled for the given package (that is, it is an instant app). So
// we don't need to go further, which is all just seeing if we should
// apply a "delayed" mode for a regular app.
return ActivityManager.APP_START_MODE_NORMAL;
}
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
: appServicesRestrictedInBackgroundLocked(uid, packageName,
packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));
if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
// This is an old app that has been forced into a "compatible as possible"
// mode of background check. To increase compatibility, we will allow other
// foreground apps to cause its services to start.
if (callingPid >= 0) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
if (proc != null &&
!ActivityManager.isProcStateBackground(proc.curProcState)) {
// Whoever is instigating this is in the foreground, so we will allow it
// to go through.
return ActivityManager.APP_START_MODE_NORMAL;
}
}
}
return startMode;
}
}
return ActivityManager.APP_START_MODE_NORMAL;
}

And the method appRestrictedInBackgroundLocked() (which is also called from appServicesRestrictedInBackgroundLocked() as fallback) decides about the startMode:

// Unified app-op and target sdk check
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Apps that target O+ are always subject to background check
if (packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
// ...and legacy apps get an AppOp check
int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
uid, packageName);
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
default:
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
}

But the final decision about foreground or background is done in ActivityManager.isProcStateBackground(uidRec.setProcState):

/** @hide Should this process state be considered a background state? */
public static final boolean isProcStateBackground(int procState) {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}

So, this section of the first method here gets the current state of foreground or background:

                ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}


Related Topics



Leave a reply



Submit