Handling volume change on android lockscreen? - android

What I am trying to do is, being able to catch volume up/down button actions on lockscreen on android 4.4.
Google Cast Design Checklist document describes lock screen requirement "Provide access to the volume control via hardware buttons". I tried various ways to handle hardware volume buttons on lock screen but none of them worked.
onKeyDown/dispatchKeyEvent - I tried to override onKeyDown as well as dispatchKeyEvent methods on Activity, but none of these are executed on lockscreen, these only works when my app is focused.
Settings.System.CONTENT_URI/ContentObserver - Registering content observer on main activity's content resolver does catch the system settings change, but that also does not occur on lockscreen.
android.intent.action.MEDIA_BUTTON - Having this filter in manifest, I am able to receive play/pause actions from lockscreen however no volume change event.
android.media.VOLUME_CHANGED_ACTION - Having this filter in manifest, this event is received in my BroadcastReceiver, unfortunately its extra values never get changed on lockscreen. When I keep hitting the volume button, the returned android.media.EXTRA_VOLUME_STREAM_VALUE remains the same (i.e. always 1), even though the CHANGED action is received by my broadcast receiver.
CastVideos-android - The reference android sender app seems to be able to control volume on receiver even when controling from senders lock screen, however even after putting breakpoitns all over the place around Cast.CastApi.setVolume(), these would not get picked. So it seems that the command is being send to a receiver from somewhere I can not locate.
I can also see some other apps being able to catch HW volume keys i.e. Play Music app. So my device surely is capable...
Can anyone suggest any working solution?

You either need to use RemoteControlClient (on pre-lollipop) or MediaSession that is introduced recently. For an example, look at CastCompanionLibrary, VideoCastManager#setUpRemoteControl() method where RCC is registered with the MediaRouter. If using MediaSession, then you need to register MediaSession with MediaRouter.

At the end it appeared I was missing one line of code:
MediaRouter.getInstance(context).addRemoteControlClient(remoteControlClient);
or
MediaRouter.getInstance(activity).setMediaSession(session.getMediaSession());
here is some more code from my implementation for 4.0+:
import android.media.AudioManager;
import android.media.RemoteControlClient;
import android.support.v7.media.MediaRouter;
remoteControlClient = new RemoteControlClient(pendingIntent);
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.registerMediaButtonEventReceiver(receiver);
audioManager.registerRemoteControlClient(remoteControlClient);
MediaRouter.getInstance(context).addRemoteControlClient(remoteControlClient);
and for 5.0+:
import android.media.AudioManager;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v7.media.MediaRouter;
session = new MediaSessionCompat(activity, TAG);
session.setPlaybackToLocal(AudioManager.STREAM_MUSIC);
MediaRouter.getInstance(activity).setMediaSession(session.getMediaSession());
The interesting thing is, there is some black magic that controls the receiver volume internally and your own Cast.CastApi.setVolume() call is not involved at all

Related

Is it possible to determine if the screen is now immersive or not, even if not in the foreground?

Background
I'm investigating how to replace the UI of the dialer, when the user gets a phone call (incoming/outgoing phone calls).
This seems to be possible in 2 ways:
On old Android versions, it's possible by having an on top view that is triggered by PHONE_STATE_CHANGED and NEW_OUTGOING_CALL Intents.
On new Android versions (at least API 23 - Android M-6.0) , it's possible by extending InCallService, and provide an Activity that will be opened from there. I've even found an example to do it, here.
The problem
Looking at how the built in dialer works, if the user is using an app in immersive mode (a game or a YouTube video on landscape), and now the device is ringing (incoming call), there is a notification instead of a full screen Activity.
But I can't find how to do it using the API.
What I've tried
I've tried to look at all the docs of InCallService. I've also tried to see if I can differentiate between immersive and non-immersive, by checking the various values I get from WindowManager:
val wm = getSystemService(Service.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
val metrics = DisplayMetrics()
display.getMetrics(metrics)
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
val realSize = Point()
display.getRealSize(realSize)
val size = Point()
display.getSize(size)
Log.d("AppLog", "mode:${display.mode} state:${display.state} size:$size realSize:$realSize metrics:$metrics realMetrics:$realMetrics rotation:${display.rotation}")
I guess it might be possible to check it using accessibility service (not sure how though), but this seems like too much to do for it.
The question
Is it possible for the app to check if the screen is now in immersive mode?
If it is possible, how, and from which Android version is it supported?
If it's not possible, how come the built in app knows how to check it?
You should post your incoming call as a Notification.
In your notification builder, use a Pending intent to launch the full-screen version of your incoming call UI:
builder.setContentIntent(pendingIntent);
builder.setFullScreenIntent(pendingIntent, true);
The system will show the heads up notification if there is any ongoing activity (and it should take into account immersive mode). If the user's device is off and on the lock screen, the full screen intent would be used to launch a full-screen UI.

Detect the time when a phone starts ringing for outgoing calls

How do detect the time when a phone starts ringing for outgoing calls.
I am developing an Application in which I am trying to make a call programatically and when call is connected (ringing). I want get back some response(Like connected). I am trying below code, its able to make call but don't know how to get response.
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"mobile no."));
startActivity(callIntent);
Thanks in advance for your time and consideration.
Edit : When outgoing call is connect (Ringing state) then i want get back a notification. like a toast or anything else which is relevant. is it possible?
I know its been a while but i hope to be helpful for someone still looking for a solution!
I recently had to work on a similar project where i needed to capture the ringing state of an outgoing call and the only way i could find was using the hidden Api of native dial-up App. This would only be possible for android > 5.0 because of the api changes. This was tested on Android 5.0.1, and worked like a charm. (p.s. you would need a rooted device for it to work, because you need to install your app as a System application (google how!) which will then be able to detect the outgoing call states).
For the record, PhoneStateListener doesn't work for detecting the outgoing call states as mentioned in many posts.
First, add this permission in the manifest file,
<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
Then define you broadcastreceiver, (here is a sample code!)
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
public class MyBroadcastReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, final Intent intent)
{
switch (intent.getIntExtra("foreground_state", -2)) {
case 0: // PreciseCallState.PRECISE_CALL_STATE_IDLE:
System.out.println("IDLE");
break;
case 3: // PreciseCallState.PRECISE_CALL_STATE_DIALING:
System.out.println("DIALING");
break;
case 4: // PreciseCallState.PRECISE_CALL_STATE_ALERTING:
System.out.println("ALERTING");
break;
case 1: // PreciseCallState.PRECISE_CALL_STATE_ACTIVE:
System.out.println("ACTIVE");
break;
}
}
}
I replaced some of the constants with their values because I saw a lot of confusion among the folks unfamiliar with the concept of reflection (for ease).
Alerting is basically the state when receiver is actually ringing! and that does not include the call setup time! so you can get the time in that case statement by using,
System.currentTimeMillis();
or some other format you would prefer!
You will have to use a BroadcastReceiver to listen for the call event and then handle it when it gets fired. Here is a nice tutorial I found on Google.
http://looksok.wordpress.com/2013/04/13/android-broadcastreceiver-tutorial-detect-outgoing-phone-call-event/
It's impossible to detect by regular non-system application - no Android API.
I could not find a way, I was googling the solution within very long time :-(
There is NO way (sadly, because I need it) to detect the time of an outgoing call starts ringing.

Android: Make app transparent and act on the background application

Is it feasible to make our Android application completely transparent (as if its not active at all) and work on the other apps?
My Requirement:
First last an app and after some few settings, make it transparent. Once its transparent, the user will not know that this app is active, however, our app should respond to only specific controls.
This is because of the Broadcast receiver limitation, I will have to use the Volume button for some actions in my application. But, this button doesn't broadcast. So, currently I am using Power button which is not the requirement.
Please throw some light on this. I did some research but, couldnt find any. :(
This is because of the Broadcast receiver limitation, I will have to use the Volume button for some actions in my application. But, this button doesn't broadcast.
I am not sure it this is right. If you read Android BroadCastReceiver for volume key up and down question, it seems that you can detect it in BroadCastRceiver. I've never tried but it might be worth a try. Do something like following:
In your BroadcastReceiver onReceive function, do something like following:
public void onReceive(Context arg0, Intent intent) {
if (intent!=null){
int volume = (Integer)intent.getExtras().get("android.media.EXTRA_VOLUME_STREAM_VALUE");
// Get the old volume from the SharedPOreferences
// volume variable contains the current volume
// Compare it to the old value saved. If it is greater than old value then user pressed the UP Volume button else DOWN volume.
}
}
Also I am not sure that you can make it transparent and still keep it running. You can have a background of an activity as transparent though. How do I create a transparent Activity on Android?. Hope it helps.

How to capture key events from bluetooth headset with android

My app can be controlled by normal headset. It simply overrides "onKeyDown". But key events from bluetooth headset are not captured - why? Or how to capture bluetooth key events?
the "log cat" shows the following if i press button on headset:
Bluetooth AT recv(3043): AT+VGS=15
AudioPolicyManagerBase(13654): FM radio recording off
AudioService(2261): sendVolumeUpdate, isKeyguardLocked...Not to update Volume Panel.
VolumePanel(2261): change volume by MSG_VOLUME_CHANGED
VolumePanel(2261): onVolumeChanged(streamType: 6, flags: 0)
VolumePanel(2261): Call setChangeSeekbarColor(false)
i also tried to handle media button actions but this isn't working. my idea is a free configurable key mapping: the user chooses "set key" my app hears on all keys (hardware, media buttons, bluetooth headset) then the user presses a key and the event/key code is stored in config.
Summerizing not working Answers:
Volume buttons must be captured by "VOLUME_CHANGED_ACTION". The problem is this intents are broadcasted to other apps and abortBroadcast() doesn't work (it works only for "ordered" Broadcasts). Another problem is that keys on cable headset and on phone trigger onReceive() twice (why?) the bluetooth headset trigger it once.
The next Problem is the 3rd key on Bluetooth headset. It triggers voice-command (s-voice starts on s3), i tried to capture many different intents regarding this but i can't "receive" this button press and don't know why.
At the end i want capture all kinds of buttons and don't want them handled by other apps (like using onKeyDown and returning true).
Add a broadcast listener to MEDIA_BUTTON:
<intent-filter android:priority="<some number>">
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
You should register your broadcast receiver inside your application (not in manifest file). Otherwise Google Music player will catch your broadcast and aboard it.
Your IntentFilter priority should be higher that other media players priorities in your phone)
Add android.permission.BLUETOOTH permission in manifest to support Bluetooth headset
After received you key you have to manually abort the broadcast using abortBroadcast().
However priorities and abortBroadcast() work fine as long as each app only responds while
e.g. something is played.
But several users also expect a "default player" to be launched (or start playing) upon button press, like the default player, so it might happen some app with a higher priority number won't let the intent come through to your app
In the onReceive, you can get the button event with
KeyEvent key = (KeyEvent)
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
key.getKeyAction() tells you whether the button was released or pressed, key.getKeyCode() tells which button is pressed.
If you want to handle single button cable headsets as well, also
regard the key code KEYCODE_HEADSETHOOK
Override the onKeyDown method in any activity and check for
the KeyEvent.KEYCODE_MEDIA_KEYCODE_pressed_key
e.g.
boolean onKeyDown(int keyCode, KeyEvent event) {
AudibleReadyPlayer abc;
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
// code for fast forward
return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
// code for next
return true;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
// code for play/pause
return true;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
// code for previous
return true;
case KeyEvent.KEYCODE_MEDIA_REWIND:
// code for rewind
return true;
case KeyEvent.KEYCODE_MEDIA_STOP:
// code for stop
return true;
}
return false;
}
Volume key integration example
Android - Volume Buttons used in my application
This one may need permission
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
Or you can try slimier implementations over the following link
Android Developer Blog : Handling remote control buttons
Android Tales : Add Headset button support to your Android application
Check out this article. It explains how to implement something similar using media button actions.
I know you've mentioned that you walked this way without success, still give it a try. Point your attention to gotchas related to registering broadcast receiver, setting intent filter priorities and adding permissions (all explained in the article).
Hope this will help.
If you are trying to listen for this from an activity, use onKeyDown() and catch the KEYCODE_MEDIA_PLAY_PAUSE (KEYCODE_MEDIA_PLAY and KEYCODE_MEDIA_PAUSE post ICS) key events.
Also for the broadcast receiver solution to work make sure there arent other apps installed and running in the background that catch these events which might take priority over yours.
Also look at this: Android - registering a headset button click with BroadcastReceiver

Clever way to wakeup Android background service on shake

I want my Android background to go into sleep mode - but then wake up when the user starts moving.
However, if I use the accelerometer in NORMAL mode (the lowest sample rate ~ 5Hz) I fear it would still consume too much power.
The best way to do it so far is on USER_PRESENT - screen on and unlocked.
Not even screen on(possibly with keyguard present) works, because, as many of you may know, there are plenty of bad apps out there that will hold a wakelock and start the screen from time to time.
I am contemplating having the user push the volume up/down buttons..
Is there any better solution to this?
don't know if you're still looking for a way to do this, but i discovered (by accident) that you can start a shakeListener, and your app will get the events, even when in the background.
(and by "by accident", i mean that i did not want my app playing the sound that it is supposed to play when the app is in the background, but even when had another app in the background, and then even put the phone to sleep, when i would walk with the phone in my pocket, it was enough shaking to cause the app to perform the operation in the background.)
/**
* load and set up the listener for shake detection
*/
private void loadShaker() {
mShaker = new ShakeListener(this);
mShaker.setOnShakeListener(new ShakeListener.OnShakeListener () {
public void onShake() {
if (!mActivityPaused)
performMyOperationCausedByShake();
}
});
}
you could probably thus set this up so that performMyOperationCausedByShake() performs an intent that causes your desired Activity to happen.
(it might be the case that this is not quite sensitive enough for what you're looking for …)

Categories

Resources