WindowManager addView - Android 7.1.1 - android

I'm adding a small TextView at the bottom of my app when the app goes offline. So I have a BroadcastReceiver that monitors network connectivity changes and in the onReceive, I show the banner. Here is the banner class which adds the TextView on top of the existing view:
public static void show() {
if (!isShowing && !isAppBackgrounded()) {
MyApplication app = MyApplication.getInstance();
WindowManager windowManager = (WindowManager) app.getSystemService(Context.WINDOW_SERVICE);
Resources res = app.getResources();
TextView offlineTv = app.getOfflineTv();
if (offlineTv.getWindowToken() != null) {
return;
}
offlineTv.setText("Offline");
offlineTv.setTextColor(ContextCompat.getColor(app, R.color.yellow));
offlineTv.setGravity(Gravity.CENTER);
offlineTv.setBackgroundColor(ContextCompat.getColor(app, R.color.dark_grey));
offlineTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, app.getResources().getInteger(R.integer.offline_banner_text_size));
WindowManager.LayoutParams params = createLayoutParams(WindowManager.LayoutParams.TYPE_TOAST, null);
windowManager.addView(offlineTv, params);
isShowing = true;
}
}
Here is the createLayoutParams method
private static WindowManager.LayoutParams createLayoutParams(int type, #Nullable IBinder windowToken) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.format = PixelFormat.TRANSLUCENT;
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = 25;
layoutParams.gravity = GravityCompat.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, ViewCompat.LAYOUT_DIRECTION_LTR);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.type = type;
layoutParams.token = windowToken;
layoutParams.windowAnimations = android.R.style.Animation_Toast;
return layoutParams;
}
This code works fine on all the devices but 7.1.1 devices. In 7.1.1 device, the TextView shows for a while and then disappears. There is just a blank white space instead of the TextView on 7.1.1 devices. Any idea why is it happening?
EDIT: as asked in the comment, here is how I get the TextView: This is MyApplication class extending Application:
TextView offlineTv = null;
/** Get the TextView to show the offline message */
public TextView getOfflineTv() {
if (offlineTv == null) {
offlineTv = new TextView(this);
}
return offlineTv;
}
/** Clear the offline TextView once we are done showing it */
public void clearOfflineTv() {
if (offlineTv != null) {
offlineTv = null;
}
}
And this is my BroadcastReceiver, where I show / hide it:
public class DSConnectionChangeReceiver extends BroadcastReceiver {
/**
* Connection-changed callback
* #param context Context
* #param intent Intent
*/
#Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean connected = false;
boolean isCellularData = false;
if (activeNetworkInfo != null) {
connected = activeNetworkInfo.isAvailable() && activeNetworkInfo.isConnected();
int type = activeNetworkInfo.getType();
isCellularData = (type == ConnectivityManager.TYPE_MOBILE) || (type == ConnectivityManager.TYPE_MOBILE_DUN);
}
if (connected) {
if (OfflineBanner.isShowing()) {
OfflineBanner.dismiss();
}
} else {
OfflineBanner.show();
}
}
}

The problem is caused by you adding the android.R.style.Animation_Toast windowAnimation. When the animation finishes on an actual Toast the whole toast would disappear. In this case your view is in the hierarchy so instead of disappearing, it becomes blank.
What you should do is leave layoutParams.windowAnimations off of the params and instead create and attach the view with the visibility set to View.GONE then animate the view onto the screen manually
Manually animating the view can be achieved with the following utility:
Animation animIn = AnimationUtils.makeInAnimation(context, true);
textView.setAnimation(animIn);
textView.setVisibility(View.VISIBLE);
textView.animate();
Snackbar Alternative:
public final class ConnectionBar {
private static boolean mIsConnected = true; //static to preserve state
private static ConnectionReceiver mReceiver; //static to detect leaks
private static SnackBar mSnack;
private ConnectionBar() { /* required */ )
public static void prepare(Context ctx) {
if (mReceiver != null) {
Log.e(TAG, "WARNING previous ConnectionBar was leaked");
}
mReceiver = new ConnectionReceiver();
ctx.registerBroadcastReceiver(mReceiver);
if (!mIsConnected) { //static so will remember from last screen
showBar(ctx);
}
}
private static void showBar(Context ctx) {
if (mSnack == null) {
mSnack = Snackbar.make(view, message, SnackBar.LENGTH_INDEFINITE);
mSnack.show();
}
}
public static void release(Context ctx) {
if (mReceiver != null) {
ctx.unregisterBroadcastReceiver(mReceiver);
mReceiver = null;
}
if (mSnack != null) {
mSnack.dismiss();
}
}
private static class ConnectionReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isCellularData = false; //migrate this how you want
if (activeNetworkInfo != null) {
ConnectionBar.mIsConnected = activeNetworkInfo.isAvailable() && activeNetworkInfo.isConnected();
int type = activeNetworkInfo.getType();
isCellularData = (type == ConnectivityManager.TYPE_MOBILE) || (type == ConnectivityManager.TYPE_MOBILE_DUN);
}
}
if (connected && ConnectionBar.mSnack != null) {
ConnectionBar.mSnack.dismiss(); //check this, might need to wrap in runOnUiThread
} else {
ConnectionBar.showBar(context);
}
}
}
Then in your activity:
public void onResume() {
ConnectionBar.prepare(this); //takes care of setting br too
}
public void onPause() {
ConnectionBar.release(this);
}

If you want View in WindowManager remains more than BroadcastReciever Lifecycle you need to do it inside a class extends of service. check out this tutorial
I think still there is problems with Lifecycle. How you use it and how the system handles it.
If you want to force system not to kill your service (and not to remove WindowManager) you have 3 options.
return OnStartCommand with the proper flag.
return Service.START_REDELIVER_INTENT;
add foreground notification
startForeground(123, NotificationFunction());
and If you have lots of processes to do add Accessibility Service. check out this

This is an intented behaviour since Android 7.1 to prevent apps from using a toast view to overlay other apps indefinitely. Whenever you use a TYPE_TOAST view, the system will impose a maximum of 3.5 seconds (i.e. that of a LONG toast) for the display of your view (and also change the view animation to the internal Toast style), after which your toast view will be hidden, EXCEPT where your app is the currently focused one.
To avoid crashing apps, your view still remains on the view hierarchy. In other words, you can still call removeView on it after it is hidden by the system, without causing an illegal state exception.
(Reference: See the commit message to the Android source:
https://github.com/aosp-mirror/platform_frameworks_base/commit/aa07653d2eea38a7a5bda5944c8a353586916ae9 )
To display a view over other apps on Android 7.1 or above you may need to request the SYSTEM_ALERT_WINDOW permission, prompt the user to get the Draw Over Apps permission, and use another view type, such as TYPE_SYSTEM_OVERLAY.

Related

How to show banner ads when user connects to Internet in the middle of app usage in Android?

Consider the following flow:
1) User opens app when there is no internet connection on his device. (So, no banner ad displayed)
2) When using the app, user connects to Internet.
In the above situation, my banner ad does not automatically load on Internet connection establishment. I want the banner ad to be displayed after step 2.
What is the best way to do this?
To further elaborate, in order for the banner ad to be displayed in the app right now, I have to end up restarting the app, this time, with the Internet connectivity present. In other words, if the app is started without an internet connection, banner ads are just not displayed.
You can Internet connectivity state change by using this class(just add this class as it is)
public class NetworkStateReceiver extends BroadcastReceiver {
protected List<NetworkStateReceiverListener> listeners;
protected Boolean connected;
public NetworkStateReceiver() {
listeners = new ArrayList<NetworkStateReceiverListener>();
connected = null;
}
public void onReceive(Context context, Intent intent) {
if(intent == null || intent.getExtras() == null)
return;
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = manager.getActiveNetworkInfo();
if(ni != null && ni.getState() == NetworkInfo.State.CONNECTED) {
connected = true;
} else if(intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,Boolean.FALSE)) {
connected = false;
}
notifyStateToAll();
}
private void notifyStateToAll() {
for(NetworkStateReceiverListener listener : listeners)
notifyState(listener);
}
private void notifyState(NetworkStateReceiverListener listener) {
if(connected == null || listener == null)
return;
if(connected == true)
listener.networkAvailable();
else
listener.networkUnavailable();
}
public void addListener(NetworkStateReceiverListener l) {
listeners.add(l);
notifyState(l);
}
public void removeListener(NetworkStateReceiverListener l) {
listeners.remove(l);
}
public interface NetworkStateReceiverListener {
public void networkAvailable();
public void networkUnavailable();
}
}
when ever internet connection is changed call for an ad
see the complete usage over here
or you may also check out this question

Is there any way make Snackbar persist among activity changes?

Although Snackbar is beautiful, it doesn't persist when changing activities. This is a bummer in scenarios where I would like to confirm that a message was sent using a Snackbar, before finishing the activity. I've considered pausing the code before exiting the activity, but have found that to be a bad practice.
If what I describe isn't possible, is there any type of material design toast message? Or a way to make a rectangular toast message; one with rounded edges of a smaller radius?
To create a Snackbar with the application context which is visible across multiple activities:
Get the WindowManager as system service
Create and add a FrameLayout (rootView) with type WindowManager.LayoutParams.TYPE_TOAST and WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL to the WindowManager
Wait until on FrameLayout.onAttachedToWindow() is called in the FrameLayout (rootView)
Get the window token of the FrameLayout (rootView) with View.getWindowToken()
Create a ContextThemeWrapper with the application context and a derived #style/Theme.AppCompat
Use the new context to create an additional FrameLayout (snackbarContainer)
Add this FrameLayout (snackbarContainer) with type WindowManager.LayoutParams.TYPE_APPLICATION_PANEL and flag WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
Wait until on View.onAttachedToWindow() is called in the FrameLayout (snackbarContainer)
Create the Snackbar like normal with the FrameLayout (snackbarContainer)
Set View.onDismissed() callback to the Snackbar and remove the FrameLayouts (rootView and snackbarContainer)
Show the snackbar Snackbar.show()
Here a working wrapper (NOTE: Swipe to dismiss is not working. Maybe some one else find the correct WindowManager.LayoutParams flags to receive touch events Fixed by CoordinatorLayout):
public class SnackbarWrapper
{
private final CharSequence text;
private final int duration;
private final WindowManager windowManager;
private final Context appplicationContext;
#Nullable
private Snackbar.Callback externalCallback;
#Nullable
private Action action;
#NonNull
public static SnackbarWrapper make(#NonNull Context applicationContext, #NonNull CharSequence text, #Snackbar.Duration int duration)
{
return new SnackbarWrapper(applicationContext, text, duration);
}
private SnackbarWrapper(#NonNull final Context appplicationContext, #NonNull CharSequence text, #Snackbar.Duration int duration)
{
this.appplicationContext = appplicationContext;
this.windowManager = (WindowManager) appplicationContext.getSystemService(Context.WINDOW_SERVICE);
this.text = text;
this.duration = duration;
}
public void show()
{
WindowManager.LayoutParams layoutParams = createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_TOAST, null);
windowManager.addView(new FrameLayout(appplicationContext)
{
#Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
onRootViewAvailable(this);
}
}, layoutParams);
}
private void onRootViewAvailable(final FrameLayout rootView)
{
final CoordinatorLayout snackbarContainer = new CoordinatorLayout(new ContextThemeWrapper(appplicationContext, R.style.FOL_Theme_SnackbarWrapper))
{
#Override
public void onAttachedToWindow()
{
super.onAttachedToWindow();
onSnackbarContainerAttached(rootView, this);
}
};
windowManager.addView(snackbarContainer, createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, rootView.getWindowToken()));
}
private void onSnackbarContainerAttached(final View rootView, final CoordinatorLayout snackbarContainer)
{
Snackbar snackbar = Snackbar.make(snackbarContainer, text, duration);
snackbar.setCallback(new Snackbar.Callback()
{
#Override
public void onDismissed(Snackbar snackbar, int event)
{
super.onDismissed(snackbar, event);
// Clean up (NOTE! This callback can be called multiple times)
if (snackbarContainer.getParent() != null && rootView.getParent() != null)
{
windowManager.removeView(snackbarContainer);
windowManager.removeView(rootView);
}
if (externalCallback != null)
{
externalCallback.onDismissed(snackbar, event);
}
}
#Override
public void onShown(Snackbar snackbar)
{
super.onShown(snackbar);
if (externalCallback != null)
{
externalCallback.onShown(snackbar);
}
}
});
if (action != null)
{
snackbar.setAction(action.text, action.listener);
}
snackbar.show();
}
private WindowManager.LayoutParams createDefaultLayoutParams(int type, #Nullable IBinder windowToken)
{
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.format = PixelFormat.TRANSLUCENT;
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.gravity = GravityCompat.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, ViewCompat.LAYOUT_DIRECTION_LTR);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
layoutParams.type = type;
layoutParams.token = windowToken;
return layoutParams;
}
#NonNull
public SnackbarWrapper setCallback(#Nullable Snackbar.Callback callback)
{
this.externalCallback = callback;
return this;
}
#NonNull
public SnackbarWrapper setAction(CharSequence text, final View.OnClickListener listener)
{
action = new Action(text, listener);
return this;
}
private static class Action
{
private final CharSequence text;
private final View.OnClickListener listener;
public Action(CharSequence text, View.OnClickListener listener)
{
this.text = text;
this.listener = listener;
}
}
}
EDIT
Once SnackbarWrapper is defined you can use it like this:
final SnackbarWrapper snackbarWrapper = SnackbarWrapper.make(getApplicationContext(),
"Test snackbarWrapper", Snackbar.LENGTH_LONG);
snackbarWrapper.setAction(R.string.snackbar_text,
new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Action",
Toast.LENGTH_SHORT).show();
}
});
snackbarWrapper.show();
If you don't have a theme, you can quickly define one in styles.xml:
<style name="FOL_Theme_SnackbarWrapper" parent="#style/Theme.AppCompat">
<!--Insert customization here-->
</style>
EDIT
For those on Android Oreo getting Bad Token Exception, change TYPE_TOAST to TYPE_APPLICATION_OVERLAY. This is due to Android Oreo implementing special permissions to draw over applications. You can ask for this permissions using:
if(!Settings.canDrawOverlays(Activity.this){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, URI.parse("package:" + getPackageName()));
startActivityForResult(intent, REQ_CODE);
}
If I understand correctly, you do this:
Activity A launch Activity B to send a message
Once message is send, you display a confirmation message
You go back to Activity A
You can use SnackBar to do that by using an ActivityResult (here is a StackOverflow post with how to use it)
Here are the steps:
Activity A launch Activity B with startActivityForResult
Do your stuff on Activity B
Set your result (check the link above to understand)
Finish Activity
In Activity A, get that code in OnActivityResult and display your
SnackBar with the proper message
This allow you do display a Snackar in Activity A corresponding to result of Activity B.
Hopes it can helps your problem
Just in case somebody needs to do this in Xamarin I have adapted the accepted answer which I found really helpful.
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Widget;
using System;
public class SnackbarWrapper
{
private readonly string text;
private readonly int duration;
private readonly IWindowManager windowManager;
private readonly Context appplicationContext;
private Snackbar.Callback externalCallback;
private SnackbarAction action { get; set; }
public static SnackbarWrapper make(Context applicationContext, string text, int duration)
{
return new SnackbarWrapper(applicationContext, text, duration);
}
private SnackbarWrapper(Context appplicationContext, string text, int duration)
{
this.appplicationContext = appplicationContext;
var wm = appplicationContext.GetSystemService(Context.WindowService);
// We have to use JavaCast instead of a normal cast
this.windowManager = wm.JavaCast<IWindowManager>();
this.text = text;
this.duration = duration;
}
public void Show()
{
WindowManagerLayoutParams layoutParams = createDefaultLayoutParams(WindowManagerTypes.Toast, null);
var frameLayout = new FrameLayout(appplicationContext);
frameLayout.ViewAttachedToWindow += delegate
{
//this.onAttachedToWindow();
onRootViewAvailable(frameLayout);
};
windowManager.AddView(frameLayout, layoutParams);
}
private void onRootViewAvailable(FrameLayout rootView)
{
var ctw = new ContextThemeWrapper(appplicationContext, Resource.Style.Base_Theme_AppCompat);
CoordinatorLayout snackbarContainer = new CoordinatorLayout(ctw);
snackbarContainer.ViewAttachedToWindow += delegate
{
onSnackbarContainerAttached(rootView, snackbarContainer);
};
windowManager.AddView(snackbarContainer, createDefaultLayoutParams(WindowManagerTypes.ApplicationPanel, rootView.WindowToken));
}
private void onSnackbarContainerAttached(View rootView, CoordinatorLayout snackbarContainer)
{
Snackbar snackbar = Snackbar.Make(snackbarContainer, text, duration);
snackbar.SetCallback(new SnackbarCallbackImpl(rootView, snackbarContainer, windowManager));
if (action != null)
{
snackbar.SetAction(action.Text, action.Listener);
}
snackbar.Show();
}
private WindowManagerLayoutParams createDefaultLayoutParams(WindowManagerTypes type, IBinder windowToken)
{
WindowManagerLayoutParams layoutParams = new WindowManagerLayoutParams();
layoutParams.Format = Format.Translucent;
layoutParams.Width = ViewGroup.LayoutParams.MatchParent;
/* Si ponemos aqui WrapContent en alguna ocasion en la que haya un action largo y el texto tambien, el snackbar puede volverse como loco
* asi que usamos MatchParent. Aun asi sucede que a veces se puede mostrar en una linea o en dos el mismo texto, pero al menos no hace el temblor loco que de la otra forma*/
layoutParams.Height = ViewGroup.LayoutParams.MatchParent;
layoutParams.Gravity = GravityFlags.CenterHorizontal | GravityFlags.Bottom;
layoutParams.Flags = WindowManagerFlags.NotTouchModal;
layoutParams.Type = type;
layoutParams.Token = windowToken;
return layoutParams;
}
public SnackbarWrapper SetCallback(Snackbar.Callback callback)
{
this.externalCallback = callback;
return this;
}
public SnackbarWrapper SetAction(string text, Action<View> listener)
{
action = new SnackbarAction(text, listener);
return this;
}
}//class
internal class SnackbarAction
{
public string Text { get; set; }
public Action<View> Listener { get; set; }
public SnackbarAction(string text, Action<View> listener)
{
Text = text;
Listener = listener;
}
}
internal class SnackbarCallbackImpl : Snackbar.Callback
{
public Snackbar.Callback externalCallback { get; set; }
View rootView;
CoordinatorLayout snackbarContainer;
IWindowManager windowManager;
public SnackbarCallbackImpl(View rootView, CoordinatorLayout snackbarContainer, IWindowManager windowManager)
{
this.rootView = rootView;
this.snackbarContainer = snackbarContainer;
this.windowManager = windowManager;
}
public override void OnShown(Snackbar snackbar)
{
base.OnShown(snackbar);
externalCallback?.OnShown(snackbar);
}
public override void OnDismissed(Snackbar snackbar, int evt)
{
base.OnDismissed(snackbar, evt);
// Clean up (NOTE! This callback can be called multiple times)
if (snackbarContainer.Parent != null && rootView.Parent != null)
{
windowManager.RemoveView(snackbarContainer);
windowManager.RemoveView(rootView);
}
externalCallback?.OnDismissed(snackbar, evt);
}
}
To have a rectangular Toast, set a rectangular background for the Toast or just set a different background color for the Toast.
Refer this post where it was posted as a problem. But it your case it is a possible solution.
UPDATE: See selected answer.
The best solution to my question is using a Timer after the presenting the Snackbar and then in the run() method of the timer, starting the activity.
Snackbar.show(); // Excluded make for brevity.
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
Intent chooseVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); // Any type of content/file. Song, doc, video...
chooseVideoIntent.setType("video/*");
startActivityForResult(chooseVideoIntent, CHOOSE_VIDEO_REQUEST);
}
}, 2 * 1000);
UPDATE: I found that by using findViewById(android.R.id.content) as the view in Snackbar.make() the Snackbar persists among fragment changes.
Actually I just need to display a message and I don't need a onClickListener on the message.
If you just need to show a message look at "Myke Dev" answer in this thread which is the one I needed:
https://stackoverflow.com/a/34640942/9993413
(Don't give upvote to me, give upvote to "Myke Dev" which wrote the answer)
In user1185087 answer you must request permissions from user by opening settings and this, for me, isn't a good thing for user interactions, but I thinks is the only way if you want to show a snackbar with an onClickListener.
(Maybe you can use an activity with no background which launch a snackbar-like dialog, but it won't act just as a window_alert snackbar)

Calling a method in another Activity in Android for setting screen brightness

I have 2 Activities, MainActivity and VideoPlayerActivity. In the MainActivity I have a socket that I can connect to it from my PC using telnet. Then I can write line and it will execute the commands I send to it. for example if I write start it will start VideoPlayerActivity and plays a movie.
Now I want to control the screen brightness. In the MainActivity when the VideoPlayerActivity is not started yet, I can easily write a command in telnet like brightness=0.1 and that will set the brightness of the MainActivity to 0.1:
if(msg.startsWith("brightness="))
{
String brightText = msg.substring(msg.indexOf('=') + 1, msg.length());
BrightnessValue = Float.parseFloat(brightText);
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = BrightnessValue;
getWindow().setAttributes(lp);
if(_videoPlayerIntent != null && _videoPlayerActivity.isActive)
{
_videoPlayerActivity.setBrightnessLevel(BrightnessValue);
}
}
Now the problem is, when VideoActivity starts, it ignores the preset brightness and will use the system defined brightness. So I put this method in VideoActivity :
public void setBrightnessLevel(float value)
{
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = value;
getWindow().setAttributes(lp);
}
but as soon as I write command to change brightness the whole app stops. Because of this section in the first code I put above in the question:
if(_videoPlayerIntent != null && _videoPlayerActivity.isActive)
{
Log.d("CALLING VIDEOACTIVITY", "SET BRIGHTNESS");
_videoPlayerActivity.setBrightnessLevel(BrightnessValue);
}
Can you tell me how can I handle this situation? I need to be able to change brightness of screen when the VideoActivity is running, and my socket is in MainActivity...
This is the method in VideoActivity....I tried to make it static then the problem is I can not access getWindow() if the method is static:
public void setBrightnessLevel(float value)
{
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = value;
getWindow().setAttributes(lp);
}
You need a handle to that second Activity to set it's window brightness so i suggest you to make a model that tells the activity creation and destruction to the first Activity.
Here is the model for listening the activitys state:
public class ActivityStateListenerModel {
public interface OnActivityStateChangedListener {
void activityStarted(Activity activity);
void activityDestroyed();
}
private static ActivityStateListenerModel mInstance;
private OnActivityStateChangedListener mListener;
public static ActivityStateListenerModel getInstance() {
if(mInstance == null) {
mInstance = new ActivityStateListenerModel();
}
return mInstance;
}
public void setListener(OnActivityStateChangedListener listener) {
mListener = listener;
}
public void activityStarted(Activity activity) {
mListener.activityStarted(activity);
}
public void activityDestroyed() {
mListener.activityDestroyed();
}
}
And you need to implement OnActivityStateChangedListener interface in your MainActivity and set it to listen the changes:
public class MainActivity extends Activity implements
OnActivityStateChangedListener {
// in onCreate(...)
ActivityStateListenerModel.getInstance().setListener(this);
Then the callbacks, in those we set flag what we need to check to know is the activity still running:
private static boolean mOtherActivityStarted;
private static Activity mOtherActivity;
#Override
public void activityStarted(Activity activity) {
Log.d(TAG, "Second activity created");
mOtherActivityStarted = true;
mOtherActivity = activity;
}
#Override
public void activityDestroyed() {
Log.d(TAG, "Second activity destroyed");
mOtherActivityStarted = false;
mOtherActivity = null;
}
And when your socket reads data you just do this in your MainActivity:
if(mOtherActivity != null && mOtherActivityStarted) {
SecondActivity.setBrightnessLevel(brightnessValue, mOtherActivity);
}
Then you have that other Activity (VideoActivity), so there you need to notify the model that activity is created or destroyed:
// in onCreate(...)
// This gives the handle to MainActivity
ActivityStateListenerModel.getInstance().activityStarted(this);
// in onDestroy()
ActivityStateListenerModel.getInstance().activityDestroyed();
And the method that changes the brightess from VideoActivity (in this case this method can be anywhere because we pass the activity in the parameters):
static public void setBrightnessLevel(float value, Activity activity)
{
Window window = activity.getWindow();
if(window != null) {
Log.d(MainActivity.TAG, "Setting brightness to " + activity.getClass().getName());
WindowManager.LayoutParams lp = window.getAttributes();
lp.screenBrightness = value;
window.setAttributes(lp);
}
}
and also it's a good habit to name your variables starting with lower case letter...
(BrightnessValue => brightnessValue)

Detect splash screen exit on Unity Android/Eclipse project

I've created an Eclipse project based on a Vuforia/Unity project by following the instructions here. That's up and running.
I am adding a button to my main activity, extends from QCARPlayerActivity. That also works, however, the button sits on top of the Unity player as the Unity splash screen plays.
Is there any way to detect when the Unity splash screen exits so I don't have controls in place before the scene loads?
UPDATE 3/18/13
I've added a static boolean to my main activity in Eclipse to track splash screen completion and modified the code that adds the controls to watch the boolean.
MainActivity.java
public static boolean splashComplete = false;
private View mControlViewContainer = null; // initialized in onCreate
class QCARViewFinderTask extends TimerTask {
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
if (!QCAR.isInitialized()) return; //wait for QCAR init
//search for QCAR view if it hasn't been found
if (mQCARView == null)
{
View rootView = MainActivity.this.findViewById(android.R.id.content);
QCARUnityPlayer qcarView = findQCARView(rootView);
if (qcarView != null) {
mQCARParentView = (ViewGroup)(qcarView.getParent());
mQCARView = qcarView;
}
}
// add controls if QCAR view is located and the splash sequence is complete
if(mQCARView != null && splashComplete && mControlViewContainer != null){
mQCARParentView.addView(mControlViewContainer);
mViewFinderTimer.cancel();
mViewFinderTimer = null;
}
}
});
}
}
In Unity I created a simple script to set the static boolean in Java and attached it to the Vuforia ARCamera
SplashExit.js
function Start () {
var mainActivity = new AndroidJavaClass ("com.example.app.MainActivity");
mainActivity.SetStatic.("splashComplete",true);
}
This works fairly well in a project with a simple scene. My controls seem to load on splash exit. When I use this method with a more complicated scene, however, the controls come up a second or so before the splash screen disappears.
Is there a better place to attach my Unity script, or a better method within the script, that will more accurately reflect when the splash sequence has exited? Perhaps Jerdak's suggestion in the comments?
Adding a yield statement did the trick. Full solution follows.
SplashExit.js should be attached to the ARCamera Game object in Unity. The start method will stall until the scene has loaded up, then set splashComplete to true in MainActivity.java.
As the timer in MainActivity.java repeatedly calls the run method of QCARViewFinderTask, the control view will be added to the Unity Player parent view as splashComplete transitions to true.
MainActivity.java
public class MainActivity extends QCARPlayerActivity {
private QCARUnityPlayer mQCARView = null;
private ViewGroup mQCARParentView = null;
private Timer mViewFinderTimer = null;
private View mControlViewContainer = null;
public static boolean splashComplete = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mControlViewContainer = getLayoutInflater().inflate(R.layout.control_layout, null);
}
#Override
public void onResume() {
super.onResume();
if (mQCARView == null) {
//search the QCAR view
mViewFinderTimer = new Timer();
mViewFinderTimer.scheduleAtFixedRate(new QCARViewFinderTask(), 1000, 1000);
}
}
#Override
public void onPause() {
super.onPause();
if (mViewFinderTimer != null) {
mViewFinderTimer.cancel();
mViewFinderTimer = null;
}
}
class QCARViewFinderTask extends TimerTask {
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
if (!QCAR.isInitialized()) return; //wait for QCAR init
//search for QCAR view if it hasn't been found
if (mQCARView == null)
{
View rootView = MainActivity.this.findViewById(android.R.id.content);
QCARUnityPlayer qcarView = findQCARView(rootView);
if (qcarView != null) {
mQCARParentView = (ViewGroup)(qcarView.getParent());
mQCARView = qcarView;
}
}
// add controls if QCAR view is located and the splash sequence is complete
if(mQCARView != null && splashComplete && mControlViewContainer != null){
mQCARParentView.addView(mControlViewContainer);
mViewFinderTimer.cancel();
mViewFinderTimer = null;
}
}
});
}
private QCARUnityPlayer findQCARView(View view) {
if (view instanceof QCARUnityPlayer) {
return (QCARUnityPlayer)view;
}
if (view instanceof ViewGroup) {
ViewGroup vg = (ViewGroup)view;
for (int i = 0; i
SplashExit.js
function Start () {
yield; // wait for the scene to fully load
// Note that com.example.app.MainActivity should be updated to match your bundle identifier and class names
var mainActivity = new AndroidJavaClass ("com.example.app.MainActivity");
mainActivity.SetStatic.("splashComplete",true);
}

android on view removed from parent

on a child layout (View) is there a callback for when the view is removed from it's parent? I need to recycle some images when the view is done. I've been looking around on the web for what to do, but haven't found anything helpful yet.
I've been looking for something like this too. The best I can find is View.OnAttachStateChangeListener. I doubt it's ideal, as it's the callback for when the View is added & removed from the Window - not the parent, but it's sufficient for my needs.
Instead of registering a new listener, you can override onDetachedFromWindow in your custom View code.
I fall in that trap what marmor said:)
#Override
protected void onDetachedFromWindow() { I want to do something here, sometimes called sometimes not!!}
protected void onAttachedToWindow() {It is working fine, always}
This code is in a CustomView.
The calling code is:
contentHolder.removeAllViews();
// ... init my CustomView ...
contentHolder.addView(myCustomView);
contentHolder.requestLayout();// useless, not need
contentHolder.invalidate();// useless, not need
To understand why is not working you have to go inside Android API:
public void removeAllViews() {
removeAllViewsInLayout();
requestLayout();
invalidate(true);
}
public void removeAllViewsInLayout() {
final int count = mChildrenCount;
if (count <= 0) {
return;
}
final View[] children = mChildren;
mChildrenCount = 0;
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
boolean clearChildFocus = false;
needGlobalAttributesUpdate(false);
for (int i = count - 1; i >= 0; i--) {
final View view = children[i];
if (mTransition != null) {
mTransition.removeChild(this, view);
}
if (view == focused) {
view.unFocus(null);
clearChildFocus = true;
}
view.clearAccessibilityFocus();
cancelTouchTarget(view);
cancelHoverTarget(view);
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
addDisappearingView(view);
} else if (detach) {
view.dispatchDetachedFromWindow();
}
if (view.hasTransientState()) {
childHasTransientStateChanged(view, false);
}
dispatchViewRemoved(view);
view.mParent = null;
children[i] = null;
}
if (clearChildFocus) {
clearChildFocus(focused);
if (!rootViewRequestFocus()) {
notifyGlobalFocusCleared(focused);
}
}
}
The key is here:
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
So, if you have animation ( and in 1 case I have and in 9 cases not) it will not called the onDetachedFromWindow() and it will mess the whole UI :)
public void endViewTransition(View view) {
if (mTransitioningViews != null) {
mTransitioningViews.remove(view);
final ArrayList<View> disappearingChildren = mDisappearingChildren;
if (disappearingChildren != null && disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
if (mVisibilityChangingChildren != null &&
mVisibilityChangingChildren.contains(view)) {
mVisibilityChangingChildren.remove(view);
} else {
if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
if (view.mParent != null) {
view.mParent = null;
}
}
invalidate();
}
}
}
Again in some cases will be called even with animation.
addDisappearingView(view);
The accepted answer suggest something like this:
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View v) {
}
#Override
public void onViewDetachedFromWindow(View v) {
System.out.println("MyCustomView.onViewDetachedFromWindow");
}
});
Sadly on animation will not print the desired text.
Some important code from android.view.ViewGroup API:
void dispatchViewRemoved(View child) {
onViewRemoved(child);
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(this, child);
}
}
public void onViewRemoved(View child) {
}
So, you can override your RelativeLayout for this method.
My animation is an infinite animation, and it will not be called very soon any of the methods!
If you have an infinite animation the correct way is to write this code, when you call remove all views:
if(contentHolder.getChildCount() > 0 ){
View child0 = contentHolder.getChildAt(0);
Animation animation = child0.getAnimation();
if(animation != null) {
animation.cancel();
child0.clearAnimation();
}
}
contentHolder.removeAllViews();
Now it will be called the protected void onDetachedFromWindow()!
The Android KTX (Core KTX) library gives you a nice solution for this.
You'll need this dependency: androidx.core:core-ktx:1.3.0
You can then call a function "doOnDetach" to signal you want to run some code (once) when the view is removed from the window:
fun myInitCode() {
...
myView.doOnDetach(this::doOnMyViewDetachFromWindow)
...
}
fun doOnMyViewDetachFromWindow(view: View) {
... put your image cleanup code here ...
}
You can pass a lambda to "doOnDetach" but a method reference as shown above may be cleaner, depending on how much work you have to do.
The description of doOnDetach is as follows:
androidx.core.view ViewKt.class public inline fun View.doOnDetach(
crossinline action: (View) → Unit ): Unit
Performs the given action when this view is detached from a window. If
the view is not attached to a window the action will be performed
immediately, otherwise the action will be performed after the view is
detached from its current window. The action will only be invoked
once, and any listeners will then be removed.

Categories

Resources