Skip to content

Instantly share code, notes, and snippets.

@subtleGradient
Last active June 22, 2022 15:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save subtleGradient/ff0cd66816aa4cab881d0e27d900bcd6 to your computer and use it in GitHub Desktop.
Save subtleGradient/ff0cd66816aa4cab881d0e27d900bcd6 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash -l
SourceClass="${1:-$PWD/SandyShorts.java}"
SourceRoot="$(dirname "$SourceClass")"
ClassName="$(basename "$SourceClass" .java)"
TimeStamp=$(date -j -f "%a %b %d %T %Z %Y" "`date`" "+%s")
FakePackageName="v$TimeStamp"
# FakePackageName="$(git hash-object "$SourceClass"|cut -c1-10)_$TimeStamp"
DestinationRoot="$PWD/build"
DestinationDexFile="$SourceRoot/$ClassName.dex"
ClassPath="$ANDROID_HOME/platforms/android-16/android.jar"
if [[ -d "$DestinationRoot/$FakePackageName/$ClassName.java" ]]; then
exit 0
fi
mkdir -p "$DestinationRoot/$FakePackageName"
echo "package $FakePackageName;" > "$DestinationRoot/$FakePackageName/$ClassName.java"
cat "$SourceClass" >> "$DestinationRoot/$FakePackageName/$ClassName.java"
#TODO make javac into a server or something to make it faster
javac -classpath "$ClassPath" "$DestinationRoot/$FakePackageName/$ClassName.java" && \
"$ANDROID_HOME/platform-tools/dx" --dex --no-strict --output="$DestinationDexFile" "$DestinationRoot/$FakePackageName"/*.class && \
"$(dirname $0)/push-dex.sh" "$DestinationDexFile" "$FakePackageName/$ClassName"
#!/usr/bin/env bash -l
LocalDexFile="$1"
ClassName="$2"
RemoteDexFile="/sdcard/pushed.dex"
# "$ANDROID_HOME/platform-tools/adb" shell am force-stop "com.subtlegradient.android.thinpushclient/com.subtlegradient.android.thinpushclient.RemoteViewActivity" && sleep 1 || exit $?
"$ANDROID_HOME/platform-tools/adb" push "$LocalDexFile" "$RemoteDexFile" && \
"$ANDROID_HOME/platform-tools/adb" shell am start "com.subtlegradient.android.thinpushclient/com.subtlegradient.android.thinpushclient.RemoteViewActivity" && \
"$ANDROID_HOME/platform-tools/adb" shell am broadcast \
-a com.subtlegradient.android.thinpushclient.LOAD \
-e class "$ClassName" \
-e apk "$RemoteDexFile"
package com.subtlegradient.android.thinpushclient;
import java.io.File;
import java.lang.reflect.Method;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import dalvik.system.DexClassLoader;
public class RemoteViewActivity extends Activity {
private static final String TAG = "HotCode";
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "received intent");
String className = intent.getStringExtra("class");
String apkPath = intent.getStringExtra("apk");
if (className != null && apkPath != null)
load(apkPath, className);
else
Log.e(TAG, "intent missing 'class' extra");
}
};
private String dexOutputDir;
private File dir;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "listening for '" + "HotCode" + "'");
super.onCreate(savedInstanceState);
setContentView(new TextView(getApplicationContext()){{setText("HotCode Receiver");}});
dir = getApplicationContext().getDir("dex", 0);
dexOutputDir = dir.getAbsolutePath();
registerReceiver(receiver, new IntentFilter("HotCode"));
}
@Override
protected void onPause() {
unregisterReceiver(receiver);
super.onPause();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void load(String apkPath, String className) {
Log.d(TAG, "loading '" + className + "'");
// for (File file : dir.listFiles()) {
// Log.d(TAG, file.getAbsolutePath() + " was deleted? " + file.delete());
// }
try {
DexClassLoader classLoader = new DexClassLoader(apkPath, dexOutputDir, null, getClass().getClassLoader());
try {
Class<?> myClass = classLoader.loadClass(className);
// myClass.getConstructor().newInstance();
Log.d(TAG, "SUCCESS! loaded '" + className + "'");
Method method = myClass.getMethods()[0];
Log.d(TAG, "about to invoke '" + className + "." + method.getName() + "'");
// method.invoke(null, getApplicationContext());
method.invoke(null, RemoteViewActivity.this);
Log.d(TAG, "SUCCESS! '" + className + "." + method.getName() + "'");
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
}
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class Test {
static public void augmentRunningActivity(Activity activity) {
final Context context = activity.getApplicationContext();
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
final float scaledDensity = metrics.scaledDensity;
Log.d("thinpushclient", "\n\n\n");
Log.d("thinpushclient", Test.class.getName());
activity.setContentView(new View(context) {
float fontSize = 36f;
private TextPaint paint = new TextPaint() {
{
setStyle(Paint.Style.FILL);
setColor(0xffFFFFFF);
setTextSize(fontSize * scaledDensity);
}
};
private Paint borderPaint = new TextPaint() {
{
setStyle(Paint.Style.FILL);
setColor(0x99FF0000);
}
};
String ellipsizedText;
// String text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
// String text = "אל זכר העיר רוסית מבוקשים. שנורו בישול צ'ט אל. ספינות רשימות אחרונים שכל ב. בה ארץ אקראי לערכים. גם שדרות ביוני גיאוגרפיה היא. ויש הבקשה רומנית אם, של המשפט העברית אווירונאוטיקה היא. כימיה ייִדיש אנא של.";
// String text = "Ηας ελεστραμ ασεντιορ εα, εξ πωσε νυσκυαμ δισεντιυνθ κυο. Εα εως λιβερ αυδιαμ φεριθυς. Εως υνυμ σριψεριθ αδ, κυι θε λατινε κυαεκυε περπετυα. Φις νιβχ πρωπριαε αργυμενθυμ νε.";
// String text = "وفي الستار فرنسية الوزراء بـ. انتهت النازيين إذ أما, بسبب فقامت الألوف أم كان, بها وبعدما إستعمل الشهير من. الثالث، الهزائم حين هو, قد فرنسية الحاملات مكن, يتم بـ تسمّى الانجليزية. إذ دحر جندي وإقامة الأمريكي،. ثم حرب بالقصف للغزو،, بعد تم بهجوم كانتا إظهارإخفاء. إحتار نتيجة أي كلا, ثم للصين العناد الحرب، حرب.";
// String text = "妥ど 禞じゅ氩 䧟駺.苨儥 椦杯ゐ褩きゃ コぶ焣榟嶣, せ䦎 イげ覌 갣驌榵ウゥ䥪 椦杯ゐ褩きゃ 鏨夦以バヴィェ 氧楣 槥䣃卤勩ス キャきょしゃ餥す 僯さり焯ギョ 禤谣ね, とぢょへでびゅ 櫦ヴァ詃マ て ナた かにゅ䄥, ぬ䤣䰥 짦襃ぽ綦秚 僯さり焯ギョ 槞觟ぬ䤣䰥 にゃちゅ 礯盨樃 じ壪 堥䦪そ捦褤 ゝひょ襨ル䨦, 禯ざ㠤 堥䦪 廥ぎ饃い杦 䏦べ知礯盨 褩きゃや 盤觚わ䏩みょ 蝥はりゅぞ棃 じゃ餩ばナた やきゅ り焯ギョ 脩滧䨤ぎゅぢゅ ゝひょ襨ル䨦 餩ば, 鏨夦以バヴィェ め檣㛤杧づ ウゥ䥪䧟 褩きゃ";
String text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321`,Å";
int index = 1;
int count = 0;
boolean reverse = false;
@Override protected void onMeasure(int w, int h){
if (index >= 10 || index <= 1) { reverse = !reverse; }
paint.setTextSize(getHeight()/16 * scaledDensity * (reverse ? ++index : --index)*0.1f);
super.onMeasure(w,h);
long start;
start = System.nanoTime();
ellipsizedText = (String) TextUtils.ellipsize(text, paint, metrics.widthPixels, TextUtils.TruncateAt.MIDDLE);
Log.d("thinpushclient", TextView.class.getName() + " onMeasure " + ((System.nanoTime() - start)*0.000001) + "ms");
}
boolean goNuts = true;
@Override
public boolean onTouchEvent(MotionEvent event) {
goNuts = !goNuts;
postInvalidate();
requestLayout();
return super.onTouchEvent(event);
}
float top = 300;
float height = 0;
@Override protected void onDraw(Canvas canvas) {
++count;
long start = System.nanoTime();
top = getHeight()/2;
canvas.drawColor(0xbbaadd99);
canvas.drawText(ellipsizedText, 0, top, paint);
// borderPaint.setColor(0x55FF0000);
// canvas.drawRect(0,top + paint.ascent(), getWidth(), top, borderPaint);
// borderPaint.setColor(0x5500FF00);
// canvas.drawRect(0,top, getWidth(), top + paint.descent(), borderPaint);
// borderPaint.setColor(0x550000FF);
// canvas.drawRect(0,top - (fontSize * scaledDensity) + paint.descent(), getWidth(), top+paint.descent(), borderPaint);
//
// borderPaint.setColor(0xffFFFFFF);
// borderPaint.setStyle(Paint.Style.STROKE);
// canvas.drawRect(0,top + paint.ascent(), getWidth(), top + paint.descent(), borderPaint);
Log.d("thinpushclient", TextView.class.getName() + " onDraw " + ((System.nanoTime() - start)*0.000001) + "ms");
if (goNuts){
postInvalidate();
requestLayout();
}
if (count >= 100) {
count = 0;
goNuts = false;
}
}
});
}
}
@subtleGradient
Copy link
Author

subtleGradient commented Feb 19, 2019

  1. Create an Android app with RemoteViewActivity.java in there somehow
  2. Build, install, & run that app on one or many devices
  3. Compile Test.java using java-to-dex.sh or whatever works for you
  4. Push the generated Test.dex file to your the running app(s) and run it using push-dex.sh
    (EDIT: java-to-dex.sh runs push-dex.sh for you, so ignore step #4

@subtleGradient
Copy link
Author

For those taking notes at home, that reference to SandyShorts is the very very very first primordial exploration of what eventually became React Native. Remind me to Blast all about that one of these days.

SandyShorts has absolutely nothing to do with AoHotView other than the fact that I used the one to develop the other.

@subtleGradient
Copy link
Author

Oh also! I totally forgot.
Using this HotCode stuff, I created a view hierarchy inspector tool thing that eventually became integrated into some other Facebook dev tool that I completely forget the name of. But it was awesome!

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