How wake up device and show an activity on top of

2020-02-11 08:30发布

问题:

I have an old android app with an alarm where the user can set a time and alarm behavior like ringtone and ring duration, volume, if it only vibrates or if it's silent. It was working but it stopped working a long time ago due to new android rules and just now I had time to update it but I can't find an up-to-date answer to these questions.

Once the alarm is due I need to open an activity, on top of anything, including another app or lock screen, just like the default android alarm or an incoming call. This activity will have a message and a button to dismiss. Once dismissed I need the phone state back to where it was before.

I can set the alarm and it works, the BroadcastReceiver opens the activity if the app is in the foreground or background, but not if the app was forced to close. It pops-up the default crash message that my app stopped. Plus, I have no idea of how to make it opens on top of any lock screen.

I'm guessing it's because of missing permissions or flags.

I'm working with Xamarin so if you don't know it all you need to know is that the activities metadata are set on the class and compiled afterwards to the manifest.

Here's the activity I want to show when the alarm ends (not the main activity):

[Activity(Label = "@string/app_name", Theme = "@style/MainTheme.StopAlarm", LaunchMode = Android.Content.PM.LaunchMode.SingleTask)]
public class StopAlarmActivity : Activity
{

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        SetContentView(Resource.Layout.StopAlarmLayout);

        Bundle bundle = Intent.Extras;
        string msg = bundle.GetString("message");

        TextView nameTV = FindViewById<TextView>(Resource.Id.alarmTextView);
        nameTV.Text = msg;

        Button okButton = FindViewById<Button>(Resource.Id.okButton);
        okButton.Text = AppResources.OK;
        okButton.Click += (object sender, EventArgs args) =>
        {
            Finish();
        };
    }
}

My receiver:

[BroadcastReceiver]
    public class AlarmReceiver : BroadcastReceiver
    {

        public override void OnReceive(Context context, Intent intent)
        {
            Toast.MakeText(context, "Alarm worked.", ToastLength.Long).Show();

            string msg = intent.Extras.GetString("message");

            var myIntent = new Intent(context, typeof(StopAlarmActivity));
            Bundle bundle = new Bundle();
            bundle.PutString("message", msg);
            myIntent.PutExtras(bundle);
            myIntent.SetFlags(ActivityFlags.FromBackground);
            myIntent.SetFlags(ActivityFlags.NewTask);
            myIntent.AddCategory(Intent.CategoryLauncher);
            Forms.Context.StartActivity(myIntent);
        }
    }

Please don't waste your time telling me that this behavior should be avoided. It's an alarm, it's meant to wake him up if set by himself. Plus, the default android alarm doesn't do what my users want to do. The alarms are previously set based on some data in the app as a suggestion. The user has to run them and it's highly customizable for his needs.

回答1:

Just have to set the correct window flags. This works from API 14(?) up to Oreo Beta:

Activity:

[Activity(Label = "AlarmActivity")]
public class AlarmActivity : Activity, View.IOnClickListener
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.Alarm);
        FindViewById<Button>(Resource.Id.myButton).SetOnClickListener(this);
    }

    public override void OnAttachedToWindow()
    {
        Window.AddFlags(WindowManagerFlags.ShowWhenLocked | 
                        WindowManagerFlags.KeepScreenOn | 
                        WindowManagerFlags.DismissKeyguard | 
                        WindowManagerFlags.TurnScreenOn);
    }

    public void OnClick(View v)
    {
        Finish();
    }
}

AlarmManager Test:

Call this routine and the alarm manager will start up the AlarmActivity in one minute, so apply this within a Button click/listener and lock your screen:

using (var manager = (Android.App.AlarmManager)GetSystemService(AlarmService))
using (var calendar = Calendar.Instance)
{
    calendar.Add(CalendarField.Minute, 1);
    Log.Debug("SO", $"Current date is   : {Calendar.Instance.Time.ToString()}");
    Log.Debug("SO", $"Alarm will fire at {calendar.Time.ToString()}");
    var alarmIntent = new Intent(ApplicationContext, typeof(AlarmActivity));
    var pendingIntent = PendingIntent.GetActivity(this, 0, alarmIntent, PendingIntentFlags.OneShot);
    manager.SetExact(AlarmType.RtcWakeup, calendar.TimeInMillis, pendingIntent);
}


回答2:

You're actually using the wrong Context in the OnReceive method. Instead of doing this:

Forms.Context.StartActivity(myIntent);

try this:

context.StartActivity(myIntent);

When you force your application to close, there won't be any Forms.Context to use.

Update: If you want to show the Activity on the lock screen, you need to set showForAllUsers to true. Here's the description from the Android documentation:

Specify that an Activity should be shown even if the current/foreground user is different from the user of the Activity. This will also force the android.view.LayoutParams.FLAG_SHOW_WHEN_LOCKED flag to be set for all windows of this activity.

Earlier you would've used a much more descriptive showOnLockScreen flag but it was deprecated in API level 23.