UPDATE
Same issue present in the latest Gmail app. I still don\'t understand why would Google make such unpleasant UI change. Obsessive in me goes cr
In case only the color is the issue, you can change it. Only to a fixed color resource.
<color name="abc_input_method_navigation_guard" tools:override="true">@color/primary_dark</color>
Obvious ?colorPrimaryDark
will not work, not even on API 21.
The view responsible for the black status bar background is stored in AppCompatDelegateImplV7.mStatusGuard
. You can get the delegate by calling getDelegate()
from your activity and access mStatusGuard
field by reflection. After starting the action mode you can get a reference to this view and customize it however you like.
This was found in AppCompat 24.1.1.
The version 23.0.0 of v7 appcompat library
introduced an animation that fades in and out the action mode when it's started and finished as you can read here:
The action mode has fades in and is working as intended.
The changes are made in the method onDestroyActionMode
in AppCompatDelegateImplV7
:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
mActionModePopup.dismiss();
} else if (mActionModeView != null) {
mActionModeView.setVisibility(View.GONE);
if (mActionModeView.getParent() != null) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
}
if (mActionModeView != null) {
mActionModeView.removeAllViews();
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
In version 23.0.0 it was changed to:
public void onDestroyActionMode(ActionMode mode) {
mWrapped.onDestroyActionMode(mode);
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
endOnGoingFadeAnimation();
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mActionModeView.setVisibility(View.GONE);
if (mActionModePopup != null) {
mActionModePopup.dismiss();
} else if (mActionModeView.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
mActionModeView.removeAllViews();
mFadeAnim.setListener(null);
mFadeAnim = null;
}
});
}
if (mAppCompatCallback != null) {
mAppCompatCallback.onSupportActionModeFinished(mActionMode);
}
mActionMode = null;
}
As you can see mWrapped.onDestroyActionMode(mode);
is called immediately, not when the animation ends. This is what cause the black status bar in your app and in other apps like Gmail and Keep.
The workaround that you found works, but unfortunately is not reliable, because if the animation takes longer you could see the black status bar anyway.
I think Google should correct the issue and call onDestroyActionMode
only when the animation is really ended. In the mean time you can change this behaviour with a bit of reflections. It is necessary to override onSupportActionModeStarted
in your activity and call the method fixActionModeCallback
:
@Override
public void onSupportActionModeStarted(ActionMode mode) {
super.onSupportActionModeStarted(mode);
//Call this method
fixActionModeCallback(this, mode);
}
private void fixActionModeCallback(AppCompatActivity activity, ActionMode mode) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return;
if (!(mode instanceof StandaloneActionMode))
return;
try {
final Field mCallbackField = mode.getClass().getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
final Object mCallback = mCallbackField.get(mode);
final Field mWrappedField = mCallback.getClass().getDeclaredField("mWrapped");
mWrappedField.setAccessible(true);
final ActionMode.Callback mWrapped = (ActionMode.Callback) mWrappedField.get(mCallback);
final Field mDelegateField = AppCompatActivity.class.getDeclaredField("mDelegate");
mDelegateField.setAccessible(true);
final Object mDelegate = mDelegateField.get(activity);
mCallbackField.set(mode, new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
return mWrapped.onPrepareActionMode(mode, menu);
}
@Override
public boolean onActionItemClicked(android.support.v7.view.ActionMode mode, MenuItem item) {
return mWrapped.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(final android.support.v7.view.ActionMode mode) {
Class mDelegateClass = mDelegate.getClass().getSuperclass();
Window mWindow = null;
PopupWindow mActionModePopup = null;
Runnable mShowActionModePopup = null;
ActionBarContextView mActionModeView = null;
AppCompatCallback mAppCompatCallback = null;
ViewPropertyAnimatorCompat mFadeAnim = null;
android.support.v7.view.ActionMode mActionMode = null;
Field mFadeAnimField = null;
Field mActionModeField = null;
while (mDelegateClass != null) {
try {
if (TextUtils.equals("AppCompatDelegateImplV7", mDelegateClass.getSimpleName())) {
Field mActionModePopupField = mDelegateClass.getDeclaredField("mActionModePopup");
mActionModePopupField.setAccessible(true);
mActionModePopup = (PopupWindow) mActionModePopupField.get(mDelegate);
Field mShowActionModePopupField = mDelegateClass.getDeclaredField("mShowActionModePopup");
mShowActionModePopupField.setAccessible(true);
mShowActionModePopup = (Runnable) mShowActionModePopupField.get(mDelegate);
Field mActionModeViewField = mDelegateClass.getDeclaredField("mActionModeView");
mActionModeViewField.setAccessible(true);
mActionModeView = (ActionBarContextView) mActionModeViewField.get(mDelegate);
mFadeAnimField = mDelegateClass.getDeclaredField("mFadeAnim");
mFadeAnimField.setAccessible(true);
mFadeAnim = (ViewPropertyAnimatorCompat) mFadeAnimField.get(mDelegate);
mActionModeField = mDelegateClass.getDeclaredField("mActionMode");
mActionModeField.setAccessible(true);
mActionMode = (android.support.v7.view.ActionMode) mActionModeField.get(mDelegate);
} else if (TextUtils.equals("AppCompatDelegateImplBase", mDelegateClass.getSimpleName())) {
Field mAppCompatCallbackField = mDelegateClass.getDeclaredField("mAppCompatCallback");
mAppCompatCallbackField.setAccessible(true);
mAppCompatCallback = (AppCompatCallback) mAppCompatCallbackField.get(mDelegate);
Field mWindowField = mDelegateClass.getDeclaredField("mWindow");
mWindowField.setAccessible(true);
mWindow = (Window) mWindowField.get(mDelegate);
}
mDelegateClass = mDelegateClass.getSuperclass();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (mActionModePopup != null) {
mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
}
if (mActionModeView != null) {
if (mFadeAnim != null) {
mFadeAnim.cancel();
}
mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0.0F);
final PopupWindow mActionModePopupFinal = mActionModePopup;
final ActionBarContextView mActionModeViewFinal = mActionModeView;
final ViewPropertyAnimatorCompat mFadeAnimFinal = mFadeAnim;
final AppCompatCallback mAppCompatCallbackFinal = mAppCompatCallback;
final android.support.v7.view.ActionMode mActionModeFinal = mActionMode;
final Field mFadeAnimFieldFinal = mFadeAnimField;
final Field mActionModeFieldFinal = mActionModeField;
mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
public void onAnimationEnd(View view) {
mActionModeViewFinal.setVisibility(View.GONE);
if (mActionModePopupFinal != null) {
mActionModePopupFinal.dismiss();
} else if (mActionModeViewFinal.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeViewFinal.getParent());
}
mActionModeViewFinal.removeAllViews();
mFadeAnimFinal.setListener((ViewPropertyAnimatorListener) null);
try {
if (mFadeAnimFieldFinal != null) {
mFadeAnimFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
mWrapped.onDestroyActionMode(mode);
if (mAppCompatCallbackFinal != null) {
mAppCompatCallbackFinal.onSupportActionModeFinished(mActionModeFinal);
}
try {
if (mActionModeFieldFinal != null) {
mActionModeFieldFinal.set(mDelegate, null);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
}
});
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Nobody? Here is the workaround I came up with. Delay.
@Override
public void onDestroyActionMode(ActionMode mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
try {
getActivity().getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}, 400);
}
mActionMode = null;
}