Permalink
Browse files

initial commit.

Note. Not working in this state. Portrait/Landscape transition must be done first.
  • Loading branch information...
0 parents commit b6905243fe2834335d5e011a33a098da5d808bb3 @sebastianst committed Jul 4, 2013
@@ -0,0 +1,10 @@
+*.iml
+*.iws
+*.ipr
+.idea/
+.gradle/
+local.properties
+
+*/build/
+
+*.apk
@@ -0,0 +1,19 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.4'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 'rovo89:Xposed Bridge API:17'
+ buildToolsVersion "17.0.0"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 17
+ }
+}
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="net.cantab.stammler.snapshare"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" />
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher">
+ <meta-data android:value="true" android:name="xposedmodule"/>
+ <meta-data android:value="2.0*" android:name="xposedminversion"/>
+ <meta-data android:value="Lets you share arbitrary Images via Snapchat." android:name="xposeddescription"/>
+ <activity android:name=".ReceiveImageActivity"
+ android:theme="@android:style/Theme.NoDisplay">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
@@ -0,0 +1 @@
+net.cantab.stammler.snapshare.Snapshare
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,134 @@
+package net.cantab.stammler.snapshare;
+
+/**
+ * Created by sebastian on 6/26/13.
+ *
+ */
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import static android.graphics.Bitmap.createBitmap;
+
+import static net.cantab.stammler.snapshare.Snapshare.LOG_TAG;
+
+public class ReceiveImageActivity extends Activity {
+ private static final int JPEG_QUALITY = 85;
+
+ @Override
+ public void onCreate (Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Get intent, action and MIME type
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String type = intent.getType();
+ Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (Intent.ACTION_SEND.equals(action) && type.startsWith("image/")) {
+ Log.d(LOG_TAG, "Received Image share of type " + type
+ + "\nand URI " + imageUri.toString()
+ + "\nCalling hooked Snapchat with same Intent.");
+
+ //InputStream iStream;
+ ByteArrayOutputStream oStream = new ByteArrayOutputStream();
+ try {
+ /*iStream = getContentResolver().openInputStream(imageUri);
+ oStream = new ByteArrayOutputStream(iStream.available());
+ Log.d("Snapshare", "iStream.available(): " + iStream.available());
+ int byteSize = IOUtils.copy(iStream, oStream);
+ Log.v("Snapshare", "Image size: " + byteSize/1024 + " kB");*/
+ /*TODO use BitmapFactory with inSampleSize magic to avoid using too much memory,
+ see http://developer.android.com/training/displaying-bitmaps/load-bitmap.html#load-bitmap */
+ Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ Log.d(LOG_TAG, "Original image w x h: " + width + " x " + height);
+ // Portrait images have to be rotated 90 degrees clockwise for Snapchat to be displayed correctly
+ if (height > width) {
+ Log.d(LOG_TAG, "Portrait image detected, rotating 90 degrees clockwise.");
+ Matrix matrix = new Matrix();
+ matrix.setRotate(270);
+ bitmap = createBitmap(bitmap, 0, 0, width, height, matrix, true);
+ // resetting width and height
+ width = bitmap.getWidth();
+ height = bitmap.getHeight();
+ }
+
+ /* Scaling and cropping mayhem
+
+ Snapchat will break if the image sent does not fit into the
+ DisplayMetrics.widthPixels x DisplayMetrics.heightPixels rectangle (Display rectangle)
+ and it will scale the image up if the Display rectangle is larger than the image,
+ ignoring the image's ratio.
+
+ So, we sample the image down such that the Display rectangle fits into it and touches one side.
+ Then we crop the picture to that rectangle */
+ DisplayMetrics dm = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(dm);
+ int dWidth = dm.widthPixels;
+ int dHeight = dm.heightPixels;
+ Log.d(LOG_TAG, "Display metrics w x h: " + dWidth + " x " + dHeight);
+ // DisplayMetrics' values depend on the phone's tilt, so we normalize them to landscape mode
+ if (dHeight > dWidth) {
+ Log.d(LOG_TAG, "Normalizing display metrics to landscape mode.");
+ int temp = dWidth;
+ dWidth = dHeight;
+ dHeight = temp;
+ }
+ /* If the image properly covers the Display rectangle, we mark it as a "large" image
+ and are going to scale it down. We make this distinction because we don't wanna
+ scale the image up if it is smaller than the Display rectangle. */
+ boolean largeImage = ((width > dWidth) & (height > dHeight));
+ Log.d(LOG_TAG, "Large image? " + largeImage);
+ int imageToDisplayRatio = width * dHeight - height * dWidth;
+ if (imageToDisplayRatio > 0) {
+ // i.e., width/height > dWidth/dHeight, so have to crop from left and right:
+ int newWidth = (dWidth * height / dHeight);
+ Log.d(LOG_TAG, "New width after cropping left & right: " + newWidth);
+ bitmap = createBitmap(bitmap, (width - newWidth) / 2, 0, newWidth, height);
+
+ } else if (imageToDisplayRatio < 0) {
+ // i.e., width/height < dWidth/dHeight, so have to crop from top and bottom:
+ int newHeight = (dHeight * width / dWidth);
+ Log.d(LOG_TAG, "New height after cropping top & bottom: " + newHeight);
+ bitmap = createBitmap(bitmap, 0, (height - newHeight) / 2, width, newHeight);
+ }
+ if (largeImage) {
+ Log.d(LOG_TAG, "Scaling down.");
+ bitmap = Bitmap.createScaledBitmap(bitmap, dWidth, dHeight, true);
+ }
+ /// Scaling and cropping finished, ready to pass to Snapchat
+
+ bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, oStream);
+ Log.d(LOG_TAG, "outputStream size: " + oStream.size()/1024 + " kB = " + oStream.size() + " B");
+ byte [] imageData = oStream.toByteArray();
+ int dataLength = imageData.length;
+ Log.d(LOG_TAG, "byte array size: " + dataLength/1024 + " kB = " + dataLength + " B");
+ bitmap.recycle();
+ intent.putExtra("imageData", imageData);
+ intent.putExtra("dataLength", dataLength);
+ intent.setComponent(ComponentName.unflattenFromString("com.snapchat.android/.LandingPageActivity"));
+ startActivity(intent);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "File not found!", e);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IO Error!", e);
+ }
+ }
+ //call finish at the end to close the wrapper
+ finish();
+ }
+}
+//TODO video sharing:
+// intent.putExtra("videoFile", imageUri.toString()); LATER for video share. should be a file:// string, pointing to a .mp4 file
@@ -0,0 +1,94 @@
+package net.cantab.stammler.snapshare;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import java.lang.reflect.InvocationTargetException;
+
+import de.robv.android.xposed.IXposedHookLoadPackage;
+import de.robv.android.xposed.XC_MethodHook;
+import de.robv.android.xposed.XposedBridge;
+import de.robv.android.xposed.callbacks.XC_LoadPackage;
+
+import static de.robv.android.xposed.XposedHelpers.callMethod;
+import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+import static de.robv.android.xposed.XposedHelpers.newInstance;
+
+/**
+ * Created by sebastian on 6/26/13.
+ *
+ */
+
+public class Snapshare implements IXposedHookLoadPackage {
+ public static final String LOG_TAG = "Snapshare";
+
+ public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
+ if (!lpparam.packageName.equals("com.snapchat.android"))
+ return;
+ else
+ XposedBridge.log("Snapshare: Snapchat load detected.");
+
+ findAndHookMethod("com.snapchat.android.LandingPageActivity", lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ Object thiz = param.thisObject;
+ //Log.i(LOG_TAG, "Invoking super.onCreate()");
+ //Activity.class.getMethod("onCreate", Bundle.class).invoke(thiz, param.args[0]);
+ Intent intent = (Intent) callSuperMethod(thiz, "getIntent");
+ String type = intent.getType();
+ Log.d(LOG_TAG, "intent type: " + type);
+ //Log.i(LOG_TAG, "param.args[0] toString():" + ((Bundle) param.args[0]).toString());
+ /*if (type != null) {
+ if (type.startsWith("image/")) {
+ intent.setComponent(ComponentName.unflattenFromString("com.snapchat.android/.SnapPreviewActivity"));
+ //callSuperMethod(thiz, "startActivity", intent);
+ thiz.getClass().getMethod("startActivity", Intent.class).invoke(thiz, intent);
+ callSuperMethod(thiz, "finish");
+ }
+ }*/
+ int dataLength = intent.getIntExtra("dataLength", -1);
+ Log.d(LOG_TAG, "data length: " + dataLength);
+ if (dataLength > 0) {
+ Bitmap imageBitmap = BitmapFactory.decodeByteArray(intent.getByteArrayExtra("imageData"), 0, dataLength);
+ Class SnapCapturedEventClass = Class.forName("com.snapchat.android.util.eventbus.SnapCapturedEvent", true, thiz.getClass().getClassLoader());
+ Object captureEvent = newInstance(SnapCapturedEventClass, imageBitmap);
+ callMethod(thiz, "onSnapCaptured", captureEvent);
+ }
+ //callSuperMethod(thiz, "finish");
+ }
+ });
+ findAndHookMethod("com.snapchat.android.SnapPreviewFragment", lpparam.classLoader, "onCreateView", LayoutInflater.class, ViewGroup.class, Bundle.class, new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ Object thiz = param.thisObject;
+ Activity curActivity = (Activity) callSuperMethod(thiz, "getActivity");
+ Log.d(LOG_TAG, "Fr> Current Activity: " + curActivity.getLocalClassName());
+ Intent intent = curActivity.getIntent();
+ Log.d(LOG_TAG, "Fr> Intent type: " + intent.getType());
+ Bitmap bitmap = (Bitmap) getObjectField(thiz, "mImageBitmap");
+ Log.d(LOG_TAG, "Fr> Width: " + bitmap.getWidth() + ", Height: " + bitmap.getHeight());
+ DisplayMetrics dm = (DisplayMetrics) getObjectField(thiz, "mDisplayMetrics");
+ Log.d(LOG_TAG, "Fr> SnapPreviewActivity Display Metrics w x h: " + dm.widthPixels + " x " + dm.heightPixels);
+ }
+ });
+ }
+ /*
+ * callSuperMethod()
+ *
+ * XposedHelpers.callMethod() cannot call methods of the super class of an object, because it
+ * uses getDeclaredMethods(). So we have to implement this little helper, which should work
+ * similar to callMethod(). Furthermore, the exceptions from getMethod() are passed on.
+ * See http://forum.xda-developers.com/showpost.php?p=42598280&postcount=1753
+ */
+ private Object callSuperMethod(Object obj, String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ return obj.getClass().getMethod(methodName).invoke(obj);
+ }
+}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">Snapshare</string>
+</resources>
@@ -0,0 +1,20 @@
+<resources>
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+
+</resources>
@@ -0,0 +1 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
Binary file not shown.
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
Oops, something went wrong. Retry.

0 comments on commit b690524

Please sign in to comment.