While I'm reading the android.R.attr documentation, I found the breadCrumbTitle and breadCrumbShortTitle. What is the usage of these 2 attributes? Does android provide a breadCrumb view in platform base and if so what is it looks like? Why did these 2 attribute exist?
They're used in the PreferenceActivity:
sa.peekValue(com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
Specifically, they're set on PreferenceActivity.Header instances that are being pulled out of a preference_headers XML file:
tv = sa.peekValue(com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
header.breadCrumbTitleRes = tv.resourceId;
} else {
header.breadCrumbTitle = tv.string;
}
}
Unfortunately, there's very little documentation about what this feature does - where it shows up, how it's used on different API levels, etc. The official Settings guide doesn't even mention them.
There's also a concept of FragmentBreadCrumbs but that doesn't appear to use this attribute (and is even more sparsely documented!).
Edit: Looking further, it turns out that these features work in tandem! If the preference headers have breadcrumbs set, then those breadcrumbs are used in conjunction with the FragmentBreadCrumbs widget, assuming one exists with the id android.R.id.title, and we're in a multi-pane preferences page:
/**
* Change the base title of the bread crumbs for the current preferences.
* This will normally be called for you. See
* {#link android.app.FragmentBreadCrumbs} for more information.
*/
public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
if (mFragmentBreadCrumbs == null) {
View crumbs = findViewById(android.R.id.title);
// For screens with a different kind of title, don't create breadcrumbs.
try {
mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
} catch (ClassCastException e) {
setTitle(title);
return;
}
if (mFragmentBreadCrumbs == null) {
if (title != null) {
setTitle(title);
}
return;
}
if (mSinglePane) {
mFragmentBreadCrumbs.setVisibility(View.GONE);
// Hide the breadcrumb section completely for single-pane
View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
if (bcSection != null) bcSection.setVisibility(View.GONE);
setTitle(title);
}
mFragmentBreadCrumbs.setMaxVisible(2);
mFragmentBreadCrumbs.setActivity(this);
}
if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
setTitle(title);
} else {
mFragmentBreadCrumbs.setTitle(title, shortTitle);
mFragmentBreadCrumbs.setParentTitle(null, null, null);
}
}
Related
I have RecyclerView which contains user bookmarks. The plan is bookmarked item will be marked with certain icon. This is my code in onBindViewHolder():
// ...
if (bookmarks != null) {
for (BookmarkModel bookmarkData : bookmarks) {
if (bookmarkData.getLetterId() == letter && bookmarkData.getEntryId() == entry) {
holder.imgBookmark.setVisibility(View.VISIBLE);
} else {
holder.imgBookmark.setVisibility(View.INVISIBLE);
}
}
}
However, the RecyclerView is not showing all bookmark icons, only a few of them. Currently I have 3 bookmarks yet it only shows 1 of them. I have debug it and verified that holder.imgBookmark.setVisibility(View.VISIBLE) have been called 3 times. How to update the image properly?
I forgot to put break when the letter and entry match. Because of it, only the last match would show the icon.
if (bookmarks != null) {
for (BookmarkModel bookmarkData : bookmarks) {
if (bookmarkData.getLetterId() == letter && bookmarkData.getEntryId() == entry) {
holder.imgBookmark.setVisibility(View.VISIBLE);
break;
} else {
holder.imgBookmark.setVisibility(View.INVISIBLE);
}
}
}
I have a memory leak because of AppCompatTextView
It has no click listeners it's just a plain TexView with some text in it.
Is there anything I can do about that? Is that a bug or am I doing something wrong?
I've tried solution suggested here but that didn't helped.
It's an android framework bug. https://code.google.com/p/android/issues/detail?id=34731
It hasn't been fixed yet, even in support library.
Here is the fix:
public static void fixInputMethodManagerLeak(Context destContext) {
if (destContext == null) {
return;
}
InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) {
return;
}
String[] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
Field f = null;
Object obj_get = null;
for (int i = 0; i < arr.length; i++) {
String param = arr[i];
try {
f = imm.getClass().getDeclaredField(param);
if (!f.isAccessible()) {
f.setAccessible(true);
}
obj_get = f.get(imm);
if (obj_get != null && obj_get instanceof View) {
View v_get = (View) obj_get;
if (v_get.getContext() == destContext) { // referenced context is held InputMethodManager want to destroy targets
f.set(imm, null); // set empty, destroyed node path to gc
} else {
// Not want to destroy the target, that is, again into another interface, do not deal with, to avoid affecting the original logic, there is nothing further for the cycle
Log.e(TAG, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + v_get.getContext() + " dest_context=" + destContext);
break;
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Call it like this:
#Override
protected void onDestroy() {
super.onDestroy();
//if you get memory leak on configuration change too, remove the if clause.
if (isFinishing()) {
fixInputMethodManagerLeak(this);
}
}
Take a look at this question too.
According to this link:
https://code.google.com/p/android/issues/detail?id=179272
It seems that the leak is caused by:
It happens with anything which uses TextLine (TextView, descendants, Layout) with Spanned text. As SearchView uses a SpannableStringBuilder internally, it gets leaked.
I hope it will help you :)
I am trying to implement an AccessibilityService that records the user's actions (only click events at this point) and stores them, such that they can be replayed at a later point in time.
For this I register for AccessibilityEvent.TYPE_VIEW_CLICKED events and check if I could somehow recover them later on (when all I have is a reference to the window / root node of the activity) by using two strategies:
Get the clicked view's id and look for this id in the root node's tree
Get the clicked view's text and look for this text in the root node's tree
I have tested this in various applications and different parts of the Android system and the results have been very confusing. About half of the views were not recoverable by any of the two strategies, and some views were sometimes reported as being recoverable and sometimes not. I found out that the latter was due to a race condition, since the accessibility service runs in a different process than the application to which the clicked views belong.
My question now is whether there is a better way to get a handle to a view in an accessibility and find this view again in a later execution of the application.
Below you find the code of my AccessiblityService class:
public class RecorderService extends AccessibilityService {
private static final String TAG = "RecorderService";
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_CLICKED:
AccessibilityNodeInfo node = event.getSource();
if (node == null) {
Log.i(TAG, "node is null");
return;
}
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
Log.i(TAG, "root is null");
return;
}
// Strategy #1: locate node via its id
String id = node.getViewIdResourceName();
if (id == null) {
Log.i(TAG, "id is null");
} else {
List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByViewId(id);
if (rootNodes.size() == 1) {
Log.i(TAG, "success (via id)");
return;
} else {
Log.i(TAG, "multiple nodes with that id");
}
}
// Strategy #2: locate node via its text
CharSequence text = node.getText();
if (text == null) {
Log.i(TAG, "text is null");
} else {
List<AccessibilityNodeInfo> rootNodes = root.findAccessibilityNodeInfosByText(text.toString());
if (rootNodes.size() == 1) {
Log.i(TAG, "success (via text)");
return;
}
}
Log.i(TAG, "failed, node was not recoverable");
}
}
#Override
protected boolean onKeyEvent(KeyEvent event) {
Log.i("Key", event.getKeyCode() + "");
return true;
// return super.onKeyEvent(event);
}
#Override
public void onInterrupt() {
}
}
I am developing this on SDK Version 21 (Lollipop) and testing it on a HTC Nexus M8 and a Samsung Galaxy Note2, both showing similar results.
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;
}
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);
}