Background
I'm working on my app that is an alternative to the app manager (link here), and wish to optimize it a bit.
As it turns out, one of the slowest things on the app is its bootup, and the main reason for this is getting the app name . I intend on caching it, but I also wish to optimize the way it's being queried, if possible.
The problem
Android has two ways to get the app name: PackageManager.getApplicationLabel and ApplicationInfo.loadLabel .
both have about the same description, but I'm not sure which one should be used.
Not only that, but looking at the code of "ApplicationInfo.loadLabel" , it looks something like this:
public CharSequence loadLabel(PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
}
if (labelRes != 0) {
CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
if (label != null) {
return label.toString().trim();
}
}
if (name != null) {
return name;
}
return packageName;
}
I can't find the code of "PackageManager.getApplicationLabel", as it's abstract.
The question
Is there any difference between the two?
If there is no difference, why do we have 2 very similar methods to get the same app name? I mean, I can use either of them only if I have both applicationInfo object and the PackageManager object, but that's enough to use any of the methods...
If there is difference, which of them is better in terms of speed?
The source of 'PackageManager.getApplicationLabel' is available in 'ApplicationPackageManager.java'. It is as follows;
#Override
public CharSequence getApplicationLabel(ApplicationInfo info) {
return info.loadLabel(this);
}
ApplicationPackageManager.java
I see in AppUtils.java the same wrapping is done as follows;
/** Returns the label for a given package. */
public static CharSequence getApplicationLabel(
PackageManager packageManager, String packageName) {
try {
final ApplicationInfo appInfo =
packageManager.getApplicationInfo(
packageName,
PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_ANY_USER);
return appInfo.loadLabel(packageManager);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to find info for package: " + packageName);
}
return null;
}
Related
I am developing a floating window, which will be shown on apps I am observing using AccessibilityService.
But I don't want the window shown on other apps.
So I firstly configure the android:packageNames property of <accessibility-service>. Then find some solutions to check whether user is using apps I configured, like getRunningTasks, getRunningAppProcesses and UsageStatsManager. But they all have some shortcomings.
The question is:
Is there any way to check whether apps I configured using Accessibility is foreground currently?
OR:
Is there any way to make the window shown on apps I configured using Accessiblity and not shown on other apps?
Thanks a lot!
Thanks to idea of #ataulm !
I find a perfect way of checking whether my apps are in foreground:
set accessibility's packageName to all
set accessibility's EventTypes to typeAllMask
Java:
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null || event.getSource() == null) {
return;
}
AccessibilityNodeInfo source = event.getSource();
String className = source.getClassName().toString();
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_SCROLLED:
Log.i(TAG, "onAccessibilityEvent: scrolled");
break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
setForegroundApp(event.getPackageName());
break;
}
}
public static String sForegroundPackageName;
static final List<String> supportedPackagesName = blablablabla;
public static void setForegroundApp(CharSequence packageName) {
if (packageName != null) {
sForegroundPackageName = packageName.toString();
}
}
// use this to check.
public static boolean isForeground() {
return supportedPackagesName.contains(sForegroundPackageName);
}
I want to know if it is possible to open my s health application (of my android smartphone), from another my application (android) that I'm building now.
But I want besides open the app, also set some data to this app, for example I want to set the aims, or any data. It is possible to do this?
It is possible to do this?
Yes!!
If you know the package name of the other app, here is an example which is tested and working!!!!!
(This demonstrates how to send a simple String value,modify params as you want )
openApp(yourActivity.this,"other.app.package.name","Hey I am coming catch me");
`
public static boolean openApp(Context context, String packageName,String dataNeedToPass) {
PackageManager manager = context.getPackageManager();
try {
Intent i = manager.getLaunchIntentForPackage(packageName);
if (i == null) {
return false;
//pkg Name Not Found
}
i.addCategory(Intent.CATEGORY_LAUNCHER);
i.putExtra("KEY", dataNeedToPass);
context.startActivity(i);
return true;
} catch (Exception e) {
return false;
}
}
and in the launcher activity of the other side/app get the bundle
Bundle bundle = getIntent().getExtras();
if(bundle !=null){
String myString = bundle.getString("KEY");
}
p.s : If you want to send data make sure also the other app is modified and ready to catch!
I would like to know the purpose of foloowing two files:
frameworks/base/core/java/android/app/IActivityWatcher.aidl
[description: Callback interface to watch the user's traversal through activities.]
frameworks/base/core/java/android/app/IProcessObserver.aidl
[no description]
I am trying to build an app wherein user can decide which apps can be run during particular period of time (say, from 10am till 4pm).
Is there any way where my app will get notified if one the apps specified by the user starts? This way my app can send kill command (I am assuming that root access is available.)
It seems that IActivityWatcher has been removed beginning with JellyBean, in order to monitor which Activity is running foreground, you can use IProcessObserver as following:
mActivityManagerNative = ActivityManagerNative.getDefault();
if (mActivityManagerNative != null) {
try {
mActivityManagerNative.registerProcessObserver(mProcessObserver);
} catch (RemoteException e) {
Log.e("TAG", "onCreate() RemoteException!");
}
}
private IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
#Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
doWhatUWantHere();
}
#Override
public void onImportanceChanged(int pid, int uid, int importance) {
}
#Override
public void onProcessDied(int pid, int uid) {
}
};
P.S.
You can use following code snippets to get the package name of foreground running Activity:
private String getForegroundPackage() {
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<RecentTaskInfo> taskInfo = am.getRecentTasks(1,
ActivityManager.RECENT_IGNORE_UNAVAILABLE);
return taskInfo.isEmpty()
? null : taskInfo.get(0).baseIntent.getComponent().getPackageName();
}
I don't really understand what is happening and maybe you can give me an idea. I use getInstalledPackages() in order to get a list of all user installed applications and their permissions. Here is how I do it:
private PackageManager pm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pm = getPackageManager();
getListData();
}
private void getListData()
{
backTask = new BackTask();
backTask.execute();
}
protected class BackTask extends AsyncTask<Context, String, ArrayList<App>>
{
....
#Override
protected ArrayList<App> doInBackground(Context... param)
{
try {
//get a list of installed apps.
List<PackageInfo> packages = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS);
for (PackageInfo packageInfo : packages) {
ApplicationInfo application = packageInfo.applicationInfo;
....
}
I need to refresh the list displayed based on user interaction. The problem appears at this line in AsyncTask:
List<PackageInfo> packages = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS);
Sometimes packages contains all the installed apps sometimes is EMPTY. Let's say for example out of 3 times, the 3rd time is empty. Taking this code out of AsyncTask and put it on getListData() works every single time without any problems. The issue is that I get a little freeze because operation is not on a back thread.
So what's the deal with AsyncTask ? Am I doing something wrong ?
Basically, this exception was fixed on Android 5.1 (or at least that's what I've read somewhere), but for pre-5.1, you need to do something on your own.
You can avoid having a crash of this exception and get a full list of the installed apps by using the shell, as I've shown here:
https://code.google.com/p/android/issues/detail?id=172058
Here's the code:
public static List<PackageInfo> getInstalledPackages(Context context,int flags)
{
final PackageManager pm=context.getPackageManager();
//if it's Android 5.1, no need to do any special work
if(VERSION.SDK_INT>=VERSION_CODES.LOLLIPOP_MR1)
return pm.getInstalledPackages(flags);
//else, protect against exception, and use a fallback if needed:
try
{
return pm.getInstalledPackages(flags);
}
catch(Exception ignored)
{
//we don't care why it didn't succeed. We'll do it using an alternative way instead
}
// use fallback:
Process process;
List<PackageInfo> result=new ArrayList<>();
BufferedReader bufferedReader=null;
try
{
process=Runtime.getRuntime().exec("pm list packages");
bufferedReader=new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while((line=bufferedReader.readLine())!=null)
{
final String packageName=line.substring(line.indexOf(':')+1);
final PackageInfo packageInfo=pm.getPackageInfo(packageName,flags);
result.add(packageInfo);
}
process.waitFor();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
if(bufferedReader!=null)
try
{
bufferedReader.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
return result;
}
Check out a similar SO question here, CommonsWare argues that this method need to be involved in UI thread, As far as I can see, this is the only reasonable explanation.
I'm proposing a different explanation:
The IPC buffer runs full and on earlier Android versions (sub API15) just an empty list is returned while on newer Android versions a TransactionTooLargeException is thrown.
Also see:
PackageManager.getInstalledPackages() returns empty list
Is there a runtime check for an application to find out if it runs as part of an instrumentation test?
Background: Our application performs a database sync when starting. But that should happen only when started regularly. It especially interferes with the instrumentation tests testing the db sync. Not surprisingly.
And with all the other tests it's just a waste of CPU cycles.
A much simpler solution is check for a class that would only be present in a test classpath, works with JUnit 4 (unlike the solution using ActivityUnitTestCase) and doesn't require to send custom intents to your Activities / Services (which might not even be possible in some cases)
private boolean isTesting() {
try {
Class.forName("com.company.SomeTestClass");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
Since API Level 11, the ActivityManager.isRunningInTestHarness() method is available. This might do what you want.
If you are using Robolectric, you can do something like this:
public boolean isUnitTest() {
String device = Build.DEVICE;
String product = Build.PRODUCT;
if (device == null) {
device = "";
}
if (product == null) {
product = "";
}
return device.equals("robolectric") && product.equals("robolectric");
}
If you're using ActivityUnitTestCase, you could set a custom Application object with setApplication, and have a flag in there to switch database sync on or off? There's an example of using a custom Application object on my blog:
http://www.paulbutcher.com/2011/03/mock-objects-on-android-with-borachio-part-3/
You can pass an intent extra to your activity indicating it's under test.
1) In your test, pass "testMode" extra to your activity:
public void setUp() throws Exception {
super.setUp();
Intent activityIntent = new Intent();
activityIntent.putExtra("testMode", true);
setActivityIntent(activityIntent);
}
2) In your activity, check for testMode:
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("testMode")) {
// disable your database sync
}
d= (◕‿↼ ) Great answer, but if some library developer (like me) wants to know if the Host (or App using the library) is being tested, then try:
import android.content.pm.ApplicationInfo;
// ...
private static int wasTestRun = 0xDEAD;
/**
* Should only be used to speed up testing (no behavior change).
* #return true in tests, if Gradle has the right dependencies.
*/
public static boolean isTestRun(#NonNull Context context) {
if (wasTestRun != 0xDEAD) {
return wasTestRun != 0;
}
// Ignore release builds (as App may be using JUnit by mistake).
if (isDebuggable(context)) {
try {
Class.forName("org.junit.runner.Runner");
wasTestRun = 1;
return true;
} catch (ClassNotFoundException ignored) {
}
}
wasTestRun = 0;
return false;
}
public static boolean isDebuggable(#Nullable Context context) {
return context != null && (context.getApplicationContext()
.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
Note that I am not using any AtomicBoolean or other helpers, as it is already pretty fast (and locking may just bring the speed down).
You can try this
if (isRunningTest == null) {
isRunningTest = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<StackTraceElement> list = Arrays.asList(stackTrace);
for (StackTraceElement element : list) {
if (element.getClassName().startsWith("androidx.test.runner.MonitoringInstrumentation")) {
isRunningTest = true;
break;
}
}
}
This work for me because no actual device is running
public static boolean isUnitTest() {
return Build.BRAND.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.DEVICE.startsWith(Build.UNKNOWN) && Build.PRODUCT.startsWith(Build.UNKNOWN);
}