Created
March 14, 2022 13:12
-
-
Save phhusson/662af3573ad4fc91bb62e5fe7cde7250 to your computer and use it in GitHub Desktop.
Add rickroll Dialer option. Add rick.webm in assets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From f07ca2a26dcb0cc797dcc6fc0d2d4f16b89c481e Mon Sep 17 00:00:00 2001 | |
From: Pierre-Hugues Husson <[email protected]> | |
Date: Mon, 14 Mar 2022 09:09:28 -0400 | |
Subject: [PATCH] Add a rickroll button in heads-up notification to rickroll | |
caller | |
Change-Id: Ibe72535fb3e93f69a531723dc96ede05663ee251 | |
--- | |
assets/rick.webm | Bin 0 -> 1232413 bytes | |
.../NotificationBroadcastReceiver.java | 145 ++++++++++++++++++ | |
.../android/incallui/StatusBarNotifier.java | 32 ++++ | |
.../com/android/incallui/call/DialerCall.java | 7 + | |
4 files changed, 184 insertions(+) | |
create mode 100644 assets/rick.webm | |
diff --git a/java/com/android/incallui/NotificationBroadcastReceiver.java b/java/com/android/incallui/NotificationBroadcastReceiver.java | |
index 241d8ed48..6573e32ec 100644 | |
--- a/java/com/android/incallui/NotificationBroadcastReceiver.java | |
+++ b/java/com/android/incallui/NotificationBroadcastReceiver.java | |
@@ -36,6 +36,24 @@ import com.google.common.util.concurrent.FutureCallback; | |
import com.google.common.util.concurrent.Futures; | |
import com.google.common.util.concurrent.ListenableFuture; | |
+import com.android.incallui.call.state.DialerCallState; | |
+ | |
+import android.util.Log; | |
+import android.media.AudioAttributes; | |
+import android.media.AudioDeviceInfo; | |
+import android.media.AudioFormat; | |
+import android.media.AudioManager; | |
+import android.media.AudioTrack; | |
+import android.media.MediaExtractor; | |
+import android.media.MediaFormat; | |
+import android.media.MediaCodec; | |
+ | |
+import android.content.res.AssetFileDescriptor; | |
+ | |
+import java.nio.ByteBuffer; | |
+ | |
+import java.lang.Thread; | |
+ | |
/** | |
* Accepts broadcast Intents which will be prepared by {@link StatusBarNotifier} and thus sent from | |
* the notification manager. This should be visible from outside, but shouldn't be exported. | |
@@ -50,6 +68,9 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { | |
public static final String ACTION_DECLINE_INCOMING_CALL = | |
"com.android.incallui.ACTION_DECLINE_INCOMING_CALL"; | |
+ public static final String ACTION_RICKROLL_INCOMING_CALL = | |
+ "com.android.incallui.ACTION_RICKROLL_INCOMING_CALL"; | |
+ | |
public static final String ACTION_HANG_UP_ONGOING_CALL = | |
"com.android.incallui.ACTION_HANG_UP_ONGOING_CALL"; | |
public static final String ACTION_ANSWER_VIDEO_INCOMING_CALL = | |
@@ -90,6 +111,9 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { | |
Logger.get(context) | |
.logImpression(DialerImpression.Type.REJECT_INCOMING_CALL_FROM_NOTIFICATION); | |
declineIncomingCall(); | |
+ } else if (action.equals(ACTION_RICKROLL_INCOMING_CALL)) { | |
+ rickroll(context); | |
+ | |
} else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) { | |
hangUpOngoingCall(); | |
} else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) { | |
@@ -220,4 +244,125 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { | |
} | |
} | |
} | |
+ private void rickroll(Context context) { | |
+ CallList callList = InCallPresenter.getInstance().getCallList(); | |
+ DialerCall call = callList.getIncomingCall(); | |
+ call.enterBackgroundAudioProcessing(); | |
+ (new Thread() { | |
+ @Override | |
+ public void run() { | |
+ Log.d("PHH-Call", "Starting rickroll"); | |
+ AudioManager am = context.getSystemService(AudioManager.class); | |
+ AssetFileDescriptor pfd; | |
+ try { | |
+ pfd = context.getResources().getAssets().openFd("rick.webm"); | |
+ } catch(Exception e) { | |
+ Log.e("PHH-Call", "Failed grabbing rick.webm"); | |
+ return; | |
+ } | |
+ MediaExtractor extractor = new MediaExtractor(); | |
+ try { | |
+ extractor.setDataSource(pfd); | |
+ } catch(Exception e) { | |
+ Log.e("PHH-Call", "Failed setting extractor datasource"); | |
+ return; | |
+ } | |
+ | |
+ MediaFormat format = extractor.getTrackFormat(0); | |
+ String mime = format.getString(MediaFormat.KEY_MIME); | |
+ extractor.selectTrack(0); | |
+ | |
+ MediaCodec decoder; | |
+ try { | |
+ decoder = MediaCodec.createDecoderByType(mime); | |
+ } catch(Exception e) { | |
+ Log.e("PHH-Call", "Failed creating decoder"); | |
+ return; | |
+ } | |
+ decoder.configure(format, null, null, 0); | |
+ decoder.start(); | |
+ | |
+ AudioDeviceInfo[] devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS); | |
+ AudioDeviceInfo telephonyOut = null; | |
+ for(AudioDeviceInfo dev : devices) { | |
+ if(dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) telephonyOut = dev; | |
+ } | |
+ if(telephonyOut == null) { | |
+ Log.e("PHH-Call", "No TELEPHONY_TX audio device"); | |
+ decoder.stop(); | |
+ decoder.release(); | |
+ return; | |
+ } | |
+ | |
+ | |
+ AudioTrack playbackTrack = new AudioTrack.Builder() | |
+ .setAudioAttributes(new AudioAttributes.Builder() | |
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) | |
+ .build()) | |
+ .setAudioFormat(new AudioFormat.Builder() | |
+ .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) | |
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT) | |
+ .setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE)) | |
+ .build()) | |
+ .build(); | |
+ | |
+ playbackTrack.setPreferredDevice(telephonyOut); | |
+ playbackTrack.play(); | |
+ | |
+ boolean sawInputEOS = false, sawOutputEOS = false; | |
+ while(call.getState() == DialerCallState.ACTIVE || call.getState() == DialerCallState.INCOMING) { | |
+ int inputBufIndex = decoder.dequeueInputBuffer(50000); | |
+ if(inputBufIndex >= 0) { | |
+ ByteBuffer dstBuf = decoder.getInputBuffer(inputBufIndex); | |
+ int sampleSize = extractor.readSampleData(dstBuf, 0); | |
+ long presentationTime = 0L; | |
+ if(sampleSize < 0) { | |
+ sawInputEOS = true; | |
+ sampleSize = 0; | |
+ } else { | |
+ presentationTime = extractor.getSampleTime(); | |
+ } | |
+ decoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTime, | |
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); | |
+ if(!sawInputEOS) { | |
+ extractor.advance(); | |
+ } | |
+ } | |
+ | |
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); | |
+ int outputBufIndex = decoder.dequeueOutputBuffer(info, 50000); | |
+ if(outputBufIndex >= 0) { | |
+ ByteBuffer buf = decoder.getOutputBuffer(outputBufIndex); | |
+ byte[] chunk = new byte[info.size]; | |
+ buf.get(chunk); | |
+ buf.clear(); | |
+ | |
+ if(chunk.length > 0) { | |
+ playbackTrack.write(chunk, 0, chunk.length); | |
+ } | |
+ decoder.releaseOutputBuffer(outputBufIndex, false); | |
+ if( (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) | |
+ sawOutputEOS = true; | |
+ | |
+ } else if(outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { | |
+ MediaFormat oformat = decoder.getOutputFormat(); | |
+ playbackTrack.setPlaybackRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE)); | |
+ } | |
+ | |
+ if(sawOutputEOS) break; | |
+ } | |
+ Log.d("PHH-Call", "Stopped rickroll because of call state " + call.getState()); | |
+ playbackTrack.stop(); | |
+ playbackTrack.release(); | |
+ decoder.stop(); | |
+ decoder.release(); | |
+ try { | |
+ //TODO: Do it from main thread | |
+ call.disconnect(); | |
+ } catch(Exception e) { | |
+ Log.d("PHH-Call", "Tried disconnecting call", e); | |
+ } | |
+ } | |
+ }).start(); | |
+ } | |
} | |
diff --git a/java/com/android/incallui/StatusBarNotifier.java b/java/com/android/incallui/StatusBarNotifier.java | |
index 99ff7255e..e5076f08c 100644 | |
--- a/java/com/android/incallui/StatusBarNotifier.java | |
+++ b/java/com/android/incallui/StatusBarNotifier.java | |
@@ -27,6 +27,7 @@ import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_ | |
import static com.android.incallui.NotificationBroadcastReceiver.ACTION_HANG_UP_ONGOING_CALL; | |
import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_OFF_SPEAKER; | |
import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_ON_SPEAKER; | |
+import static com.android.incallui.NotificationBroadcastReceiver.ACTION_RICKROLL_INCOMING_CALL; | |
import android.Manifest; | |
import android.app.Notification; | |
@@ -35,6 +36,7 @@ import android.content.Context; | |
import android.content.Intent; | |
import android.content.res.Resources; | |
import android.graphics.Bitmap; | |
+import android.graphics.Color; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.graphics.drawable.Icon; | |
@@ -264,6 +266,7 @@ public class StatusBarNotifier | |
@RequiresPermission(Manifest.permission.READ_PHONE_STATE) | |
private void buildAndSendNotification( | |
CallList callList, DialerCall originalCall, ContactCacheEntry contactInfo) { | |
+ android.util.Log.d("PHH-Call", "buildAndSendNotification"); | |
Trace.beginSection("StatusBarNotifier.buildAndSendNotification"); | |
// This can get called to update an existing notification after contact information has come | |
// back. However, it can happen much later. Before we continue, we need to make sure that | |
@@ -290,6 +293,8 @@ public class StatusBarNotifier | |
call.getVideoTech().getSessionModificationState() | |
== SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; | |
final int notificationType; | |
+ | |
+ android.util.Log.d("PHH-Call", "Call state is " + callState); | |
if (callState == DialerCallState.INCOMING | |
|| callState == DialerCallState.CALL_WAITING | |
|| isVideoUpgradeRequest) { | |
@@ -439,6 +444,7 @@ public class StatusBarNotifier | |
private void createIncomingCallNotification( | |
DialerCall call, int state, CallAudioState callAudioState, Notification.Builder builder) { | |
setNotificationWhen(call, state, builder); | |
+ android.util.Log.d("PHH-Call", "Create incoming call notification"); | |
// Add hang up option for any active calls (active | onhold), outgoing calls (dialing). | |
if (state == DialerCallState.ACTIVE | |
@@ -452,6 +458,7 @@ public class StatusBarNotifier | |
addVideoCallAction(builder); | |
} else { | |
addAnswerAction(builder); | |
+ addRickrollAction(builder); | |
addSpeakeasyAnswerAction(builder, call); | |
} | |
} | |
@@ -857,6 +864,15 @@ public class StatusBarNotifier | |
return spannable; | |
} | |
+ private Spannable getActionText(String string, int color) { | |
+ Spannable spannable = new SpannableString(string); | |
+ if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { | |
+ spannable.setSpan( | |
+ new ForegroundColorSpan(color), 0, spannable.length(), 0); | |
+ } | |
+ return spannable; | |
+ } | |
+ | |
private void addAnswerAction(Notification.Builder builder) { | |
LogUtil.d( | |
"StatusBarNotifier.addAnswerAction", | |
@@ -929,6 +945,22 @@ public class StatusBarNotifier | |
.build()); | |
} | |
+ private void addRickrollAction(Notification.Builder builder) { | |
+ LogUtil.d( | |
+ "StatusBarNotifier.addRickrollAction", | |
+ "will show \"rickroll\" action in the incoming call Notification"); | |
+ android.util.Log.d("PHH-Call", "Adding rickroll button"); | |
+ PendingIntent rickrollPendingIntent = | |
+ createNotificationPendingIntent(context, ACTION_RICKROLL_INCOMING_CALL); | |
+ builder.addAction( | |
+ new Notification.Action.Builder( | |
+ Icon.createWithResource(context, R.drawable.quantum_ic_close_white_24), | |
+ getActionText( | |
+ "Rickroll", Color.YELLOW), | |
+ rickrollPendingIntent) | |
+ .build()); | |
+ } | |
+ | |
private void addHangupAction(Notification.Builder builder) { | |
LogUtil.d( | |
"StatusBarNotifier.addHangupAction", | |
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java | |
index 76d3e8b53..1b924e76a 100644 | |
--- a/java/com/android/incallui/call/DialerCall.java | |
+++ b/java/com/android/incallui/call/DialerCall.java | |
@@ -439,6 +439,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa | |
case Call.STATE_RINGING: | |
return DialerCallState.INCOMING; | |
case Call.STATE_ACTIVE: | |
+ case Call.STATE_AUDIO_PROCESSING: | |
return DialerCallState.ACTIVE; | |
case Call.STATE_HOLDING: | |
return DialerCallState.ONHOLD; | |
@@ -572,6 +573,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa | |
videoTech = null; | |
// We want to potentially register a video call callback here. | |
updateFromTelecomCall(); | |
+ android.util.Log.d("PHH-Call", "DialerCall new state is " + getState()); | |
if (oldState != getState() && getState() == DialerCallState.DISCONNECTED) { | |
for (DialerCallListener listener : listeners) { | |
listener.onDialerCallDisconnect(); | |
@@ -595,6 +597,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa | |
Trace.beginSection("DialerCall.updateFromTelecomCall"); | |
LogUtil.v("DialerCall.updateFromTelecomCall", telecomCall.toString()); | |
+ android.util.Log.d("PHH-Call", "telecom call new state is " + telecomCall.getState()); | |
videoTechManager.dispatchCallStateChanged(telecomCall.getState(), getAccountHandle()); | |
final int translatedState = translateState(telecomCall.getState()); | |
@@ -1991,4 +1994,8 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa | |
public int getPeerDimensionHeight() { | |
return peerDimensionHeight; | |
} | |
+ | |
+ public void enterBackgroundAudioProcessing() { | |
+ telecomCall.enterBackgroundAudioProcessing(); | |
+ } | |
} | |
-- | |
2.35.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What specific steps are needing to be done to implement this?