Skip to content

Instantly share code, notes, and snippets.

@phhusson
Created March 14, 2022 13:12
Show Gist options
  • Save phhusson/662af3573ad4fc91bb62e5fe7cde7250 to your computer and use it in GitHub Desktop.
Save phhusson/662af3573ad4fc91bb62e5fe7cde7250 to your computer and use it in GitHub Desktop.
Add rickroll Dialer option. Add rick.webm in assets
From f07ca2a26dcb0cc797dcc6fc0d2d4f16b89c481e Mon Sep 17 00:00:00 2001
From: Pierre-Hugues Husson <phh@phh.me>
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
@tlxxxsracer
Copy link

What specific steps are needing to be done to implement this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment