What is the private field name of actionbar context view ? - android

A Hello ,,,
I'm using robotium framework for testing android apps and I used Robotium-actionbarsherlock extension to perform clicks on contextual actionbar menu item ...
Here's the function I'm using ,,,
public void clickOnActionModeOverflowMenuItem(String text) {
Activity activity = solo.getCurrentActivity();
Log.d("aaaa", activity.toString());
if (!(activity instanceof SherlockFragmentActivity)) {
throw new IllegalStateException("This method should be called only in SherlockFragmentActivity.");
}
ActionBarContextView actionBarContextView = null;
try {
ActionBarSherlock actionBarSherlock = (ActionBarSherlock) invokePrivateMethodWithoutParameters(
SherlockFragmentActivity.class, "getSherlock", activity);
Log.d("eeee", actionBarSherlock.toString());
actionBarContextView = (ActionBarContextView) getPrivateField("mActionModeView", actionBarSherlock);
Log.d("dddd", actionBarContextView.toString());
} catch (Exception ex) {
Log.d(LOG_TAG, "Can not find methods to invoke action mode overflow button.");
}
if (actionBarContextView == null) {
Assert.fail("Contextual actionbar is not shown.");
}
actionBarContextView.showOverflowMenu();
sleeper.sleep();
clicker.clickOnText(text, false, 1, true, 0);
}
So, I'm asking about this line ..
actionBarContextView = (ActionBarContextView) getPrivateField("mActionModeView", actionBarSherlock);
How can I know the private field name ?
As the function fails and throws the assertion fail of null actionBarContextView ...

You can download the source code for ActionBarSherlock, which should show you what you are looking for.

Related

AppCompatTexView leaks memory on API 22

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 :)

Android: AbsListView.setItemChecked() cause ActionMode Destroy and Recreate?

If you call AbsListView.setItemChecked() directly, it works well, and the ActionMode will activate and create.
mGridView.setItemChecked(pPosition, true);
But when you call View.startActionMode() first, then call AbsListView.setItemChecked(), the ActionMode create by startActionMode() will destroy, and recreate a new one by setItemChecked().
My question is: How to avoid this issue when call View.startActionMode() first?
Looking forward to your reply! Thanks!
Why recreate a new one? See the source code of AbsListView.setItemChecked(int position, boolean value) method, you can see following codeļ¼š
// Start selection mode if needed. We don't need to if we're unchecking something.
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
if (mMultiChoiceModeCallback == null ||
!mMultiChoiceModeCallback.hasWrappedCallback()) {
throw new IllegalStateException("AbsListView: attempted to start selection mode " +
"for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
"supplied. Call setMultiChoiceModeListener to set a callback.");
}
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
That means if mChoiceActionMode == null, it will call startActionMode(mMultiChoiceModeCallback), so will recreate a new ActionMode.
And how to fix?
Here is a simple way: use reflect to assign a ActionMode create by startActionMode() to the private field mChoiceActionMode in AbsListView.
private void startActionMode() {
// Get the field "mMultiChoiceModeCallback" instance by reflect
AbsListView.MultiChoiceModeListener wrapperIns = null;
try {
Field wrapper = null;
wrapper = AbsListView.class.getDeclaredField("mMultiChoiceModeCallback");
wrapper.setAccessible(true);
wrapperIns = (AbsListView.MultiChoiceModeListener) wrapper.get(mMessageGridView);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
// Start the ActionMode, but not select any item.
ActionMode actionMode = mMessageGridView.startActionMode(wrapperIns);
// Assign actionMode to field "mChoiceActionMode" by reflect
try {
Field mChoiceActionMode = null;
mChoiceActionMode = AbsListView.class.getDeclaredField("mChoiceActionMode");
mChoiceActionMode.setAccessible(true);
mChoiceActionMode.set(mMessageGridView, actionMode);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
Why here we use wrapper? Because AbsListView.setMultiChoiceModeListener(MultiChoiceModeListener listener) will wrap our mMultiChoiceModeListener, so we can't not use directly.

Tabs appear above ActionBar when calling setDisplayShowHomeEnabled(false) in 4.0.3

I use ActionBar tab navigation mode and a ViewPager. This works fine in newer versions of Android, but on Android 4.0.3, tabs is in the top of the ActionBar instead of below.
If I use ActionBar.SetDisplayShowHomeEnabled(true), the problem appears to be fixed. However, up navigation then will not work.
My code looks as follows
ActionBar.SetTitle(Resource.String.DocumentDetails);
ActionBar.SetDisplayHomeAsUpEnabled(true);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
SetContentView(Resource.Layout.Main);
ActionBar.Tab tab = ActionBar.NewTab();
tab.SetText("tab1");
tab.TabSelected += (sender, args) => {
// Do something when tab is selected
};
ActionBar.AddTab(tab);
tab = ActionBar.NewTab();
tab.SetText("tab2");
tab.TabSelected += (sender, args) => {
// Do something when tab is selected
};
ActionBar.AddTab(tab);
Is this on an actual device? I have tested your code in an Emulator and can't reproduce. However, it does seem that several other people reported this to Google: https://code.google.com/p/android/issues/detail?id=36191
So a couple of ideas to fix this. Instead of using SetDisplayHomeAsUpEnabled try using the DisplayOptions property instead:
ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowHome |
ActionBarDisplayOptions.HomeAsUp |
ActionBarDisplayOptions.ShowTitle;
The important one is the ActionBarDisplayOptions.ShowHome in this case, which forces the appearance of a home icon and ActionBarDisplayOptions.HomeAsUp does what SetDisplayHomeAsUpEnabled(true) does. The last one I suppose you can guess.
If that does not help. There was one answer in that issue thread, which suggested to use an extension method to set the tab items as embedded into the ActionBar. However, that will look as if you are running on a tablet. The code is as follows:
using Java.Lang;
using Java.Lang.Reflect;
namespace SomeNamespace
{
public class ActionBarUtils
{
public static void SetHasEmbeddedTabs(Object inActionBar, bool hasEmbeddedTabs)
{
var actionBarClass = inActionBar.Class;
if ("android.support.v7.app.ActionBarImplJBMR2" == actionBarClass.Name)
{
actionBarClass = actionBarClass.Superclass.Superclass;
}
else if ("android.support.v7.app.ActionBarImplJB" == actionBarClass.Name)
{
actionBarClass = actionBarClass.Superclass;
}
try
{
var actionBarField = actionBarClass.GetDeclaredField("mActionBar");
actionBarField.Accessible = true;
inActionBar = actionBarField.Get(inActionBar);
actionBarClass = inActionBar.Class;
}
catch (IllegalAccessException) { }
catch (IllegalArgumentException) { }
catch (NoSuchFieldException) { }
try
{
var method = actionBarClass.GetDeclaredMethod("setHasEmbeddedTabs", new[] { Boolean.Type });
method.Accessible = true;
method.Invoke(inActionBar, new Boolean(hasEmbeddedTabs));
}
catch (NoSuchMethodException) { }
catch (InvocationTargetException) { }
catch (IllegalAccessException) { }
catch (IllegalArgumentException) { }
}
}
}
Then can be used like so in your Activity:
ActionBarUtils.SetHasEmbeddedTabs(ActionBar, true);

Is there a way to disable ActionBar's show/hide animation?

I've seen this question:
Changing the ActionBar hide animation?
But it doesn't say whether it's possible to disable animation altogether.
You can now do this,
getSupportActionBar().setShowHideAnimationEnabled(false);
I fixed using the below method:
public static void disableShowHideAnimation(ActionBar actionBar) {
try
{
actionBar.getClass().getDeclaredMethod("setShowHideAnimationEnabled", boolean.class).invoke(actionBar, false);
}
catch (Exception exception)
{
try {
Field mActionBarField = actionBar.getClass().getSuperclass().getDeclaredField("mActionBar");
mActionBarField.setAccessible(true);
Object icsActionBar = mActionBarField.get(actionBar);
Field mShowHideAnimationEnabledField = icsActionBar.getClass().getDeclaredField("mShowHideAnimationEnabled");
mShowHideAnimationEnabledField.setAccessible(true);
mShowHideAnimationEnabledField.set(icsActionBar,false);
Field mCurrentShowAnimField = icsActionBar.getClass().getDeclaredField("mCurrentShowAnim");
mCurrentShowAnimField.setAccessible(true);
mCurrentShowAnimField.set(icsActionBar,null);
}catch (Exception e){
//....
}
}
}
If you use ActionBarSherlock then you can do it. See ActionBarImpl class, it has setShowHideAnimationEnabled(boolean enabled) method.

Problems creating a Popup Window in Android Activity

I'm trying to create a popup window that only appears the first time the application starts. I want it to display some text and have a button to close the popup. However, I'm having troubles getting the PopupWindow to even work. I've tried two different ways of doing it:
First I have an XML file which declares the layout of the popup called popup.xml (a textview inside a linearlayout) and I've added this in the OnCreate() of my main Activity:
PopupWindow pw = new PopupWindow(findViewById(R.id.popup), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main), Gravity.CENTER, 0, 0);
Second I did the exact same with this code:
final LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.popup, (ViewGroup) findViewById(R.layout.main) ), 100, 100, true);
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
The first throws a NullPointerException and the second throws a BadTokenException and says "Unable to add window -- token null is not valid"
What in the world am I doing wrong? I'm extremely novice so please bear with me.
To avoid BadTokenException, you need to defer showing the popup until after all the lifecycle methods are called (-> activity window is displayed):
findViewById(R.id.main_page_layout).post(new Runnable() {
public void run() {
pw.showAtLocation(findViewById(R.id.main_page_layout), Gravity.CENTER, 0, 0);
}
});
Solution provided by Kordzik will not work if you launch 2 activities consecutively:
startActivity(ActivityWithPopup.class);
startActivity(ActivityThatShouldBeAboveTheActivivtyWithPopup.class);
If you add popup that way in a case like this, you will get the same crash because ActivityWithPopup won't be attached to Window in this case.
More universal solusion is onAttachedToWindow and onDetachedFromWindow.
And also there is no need for postDelayed(Runnable, 100). Because this 100 millis does not guaranties anything
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.d(TAG, "onAttachedToWindow");
showPopup();
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d(TAG, "onDetachedFromWindow");
popup.dismiss();
}
The accepted answer did not work for me. I still received BadTokenException. So I just called the Runnable from a Handler with delay as such:
new Handler().postDelayed(new Runnable() {
public void run() {
showPopup();
}
}, 100);
use class Context eg. MainActivity.this instead of getApplicationContext()
There are two scenarios when this exception could occur. One is mentioned by kordzik. Other scenario is mentioned here: http://blackriver.to/2012/08/android-annoying-exception-unable-to-add-window-is-your-activity-running/
Make sure you handle both of them
the solution is to set the spinner mode to dialog as below:
android:spinnerMode="dialog"
or
Spinner(Context context, int mode)
tnxs RamallahDroid
See This.
Depending on the use case, for types of pop-up to display a message, setting the pop-up type to TYPE_TOAST using setWindowLayoutType() avoids the issue, as this type of pop-up is not dependent on the underlying activity.
Edit: One of the side effects: no interaction in the popup window for API <= 18, as the touchable / focusable events would be removed by the system. ( http://www.jianshu.com/p/634cd056b90c )
I end up with using TYPE_PHONE (as the app happens to have the permission SYSTEM_ALERT_WINDOW, otherwise this won't work too).
You can check the rootview if it has the token. You can get the parent layout defined from your activity xml, mRootView
if (mRootView != null && mRootView.getWindowToken() != null) {
popupWindow.showAtLocation();
}
Check that findViewById returns something - you might be calling it too early, before the layout is built
Also you may want to post logcat output for the exceptions you're getting
You can also try to use this check:
public void showPopupProgress (){
new Handler().post(new Runnable() {
#Override
public void run() {
if (getWindow().getDecorView().getWindowVisibility() == View.GONE) {
showPopupProgress();
return;
}
popup.showAtLocation(.....);
}
});
}
If you show a PopupWindow in another PopupWindow, do not use the view in first POP, use the origin parent view.
pop.showAtLocation(parentView, ... );
I had the same problem (BadTokenException) with AlertDialog on dialog.show(). I was making an AlertDialog by following some example. In my case the reason of that problem was a string
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST)
Everything became working after I removed it.
Maybe it's time for a newer solution. This methods checks 5 times every 50ms if the parent view for the PopupWindow has a token. I use it inside my customized PopupWindow.
private fun tryToShowTooltip(tooltipLayout: View) {
Flowable.fromCallable { parentView.windowToken != null }
.map { hasWindowToken ->
if (hasWindowToken) {
return#map hasWindowToken
}
throw RetryException()
}
.retryWhen { errors: Flowable<Throwable> ->
errors.zipWith(
Flowable.range(1, RETRY_COUNT),
BiFunction<Throwable, Int, Int> { error: Throwable, retryCount: Int ->
if (retryCount >= RETRY_COUNT) {
throw error
} else {
retryCount
}
})
.flatMap { retryCount: Int ->
Flowable.timer(retryCount * MIN_TIME_OUT_MS, TimeUnit.MILLISECONDS)
}
}
.onErrorReturn {
false
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ hasWindowToken ->
if (hasWindowToken && !isShowing) {
showAtLocation(tooltipLayout, Gravity.NO_GRAVITY, 100, 100)
}
}, { t: Throwable? ->
//error logging
})
}
with
companion object {
private const val RETRY_COUNT = 5
private const val MIN_TIME_OUT_MS = 50L
}
class RetryException : Throwable()
You can specify the y-offset to account for the status bar from the pw.showAtLocation method...

Categories

Resources