Adding original code from https://code.google.com/p/pinkie-live/
This commit is contained in:
parent
9e6e4b8311
commit
6fb13ea8da
49
AndroidManifest.xml
Normal file
49
AndroidManifest.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.derpfish.pinkielive"
|
||||
android:versionName="1.2.2"
|
||||
android:versionCode="5">
|
||||
|
||||
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
|
||||
<uses-feature android:name="android.software.live_wallpaper" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application android:icon="@drawable/ic_launcher"
|
||||
android:label="Pinkie Pie Live">
|
||||
|
||||
<service android:name=".PinkiePieLiveWallpaper"
|
||||
android:label="Pinkie Pie Live"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:permission="android.permission.BIND_WALLPAPER">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.wallpaper.WallpaperService" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_SCANNER_FINISHED"/>
|
||||
<data android:scheme="file" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.service.wallpaper"
|
||||
android:resource="@xml/livewallpaper" />
|
||||
|
||||
</service>
|
||||
|
||||
<activity android:label="Settings"
|
||||
android:name=".preference.PinkiePieLiveWallpaperSettings"
|
||||
android:theme="@android:style/Theme.Light.WallpaperSettings"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher">
|
||||
</activity>
|
||||
<activity android:label="Advanced Settings"
|
||||
android:name=".preference.PinkiePieLiveWallpaperSettingsAdvanced"
|
||||
android:theme="@android:style/Theme.Light">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
BIN
assets/defaultbg.jpg
Normal file
BIN
assets/defaultbg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
BIN
assets/jump1.zip
Normal file
BIN
assets/jump1.zip
Normal file
Binary file not shown.
BIN
assets/jump2.zip
Normal file
BIN
assets/jump2.zip
Normal file
Binary file not shown.
BIN
assets/jump3.zip
Normal file
BIN
assets/jump3.zip
Normal file
Binary file not shown.
BIN
assets/jump4.zip
Normal file
BIN
assets/jump4.zip
Normal file
Binary file not shown.
BIN
assets/jump5.zip
Normal file
BIN
assets/jump5.zip
Normal file
Binary file not shown.
BIN
assets/jump6.zip
Normal file
BIN
assets/jump6.zip
Normal file
Binary file not shown.
BIN
assets/jump7.zip
Normal file
BIN
assets/jump7.zip
Normal file
Binary file not shown.
BIN
ic_launcher-web.png
Normal file
BIN
ic_launcher-web.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
libs/android-support-v4.jar
Normal file
BIN
libs/android-support-v4.jar
Normal file
Binary file not shown.
20
proguard-project.txt
Normal file
20
proguard-project.txt
Normal file
@ -0,0 +1,20 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
14
project.properties
Normal file
14
project.properties
Normal file
@ -0,0 +1,14 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=Google Inc.:Google APIs:19
|
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
BIN
res/drawable-ldpi/ic_launcher.png
Normal file
BIN
res/drawable-ldpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
33
res/layout/pony_listing.xml
Normal file
33
res/layout/pony_listing.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dip"
|
||||
android:layout_marginLeft="15dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_weight="1"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingRight="?android:attr/scrollbarSize" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@id/title"
|
||||
android:layout_below="@id/title"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</RelativeLayout>
|
16
res/values/strings.xml
Normal file
16
res/values/strings.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="credits_string">
|
||||
<![CDATA[
|
||||
Pinkie Pie Live application written by melknin: <a href="mailto:melknin@gmail.com">melknin@gmail.com</a>
|
||||
<br/><br/>
|
||||
Pinkie Pie Animations by melknin, adapted from screencaps of S01E26
|
||||
<br/><br/>
|
||||
Pinkie Pie Icon - <a href="http://cptofthefriendship.deviantart.com/art/Pinkie-Pie-4th-wall-279458561">CptOfTheFriendship</a>
|
||||
<br/><br/>
|
||||
Rarity Animation Image - <a href="http://misteraibo.deviantart.com/art/Travelling-Filly-Rarity-Vector-255586607">MisterAibo</a>
|
||||
<br/><br/>
|
||||
Default Background - Pulled from The TV DB (original source unknown; if you know, tell me so I can give proper credit!)
|
||||
]]>
|
||||
</string>
|
||||
</resources>
|
4
res/xml/livewallpaper.xml
Normal file
4
res/xml/livewallpaper.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:settingsActivity="com.derpfish.pinkielive.preference.PinkiePieLiveWallpaperSettings"
|
||||
android:thumbnail="@drawable/ic_launcher"/>
|
33
res/xml/livewallpaper_settings.xml
Normal file
33
res/xml/livewallpaper_settings.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="Settings"
|
||||
android:key="livewallpaper_settings">
|
||||
|
||||
<CheckBoxPreference android:key="livewallpaper_defaultbg"
|
||||
android:defaultValue="true"
|
||||
android:summary="Use Default Background"
|
||||
android:title="Use Default Background"
|
||||
android:summaryOn="Using Default Default"
|
||||
android:summaryOff="Using Selected Image"/>
|
||||
<Preference android:key="livewallpaper_image"
|
||||
android:title="Select Background Image"
|
||||
android:summary="Select Background" />
|
||||
<com.derpfish.pinkielive.preference.PonyListPreference
|
||||
android:key="livewallpaper_pony"
|
||||
android:title="Select Pony"
|
||||
android:summary="Pick the pony to grace your screen"
|
||||
android:defaultValue="pinkie" />
|
||||
<com.derpfish.pinkielive.preference.PonyDownloadListPreference
|
||||
android:key="livewallpaper_download"
|
||||
android:title="Download More Ponies"
|
||||
android:summary="Fetch animations from the project page" />
|
||||
<PreferenceScreen android:title="Advanced Settings"
|
||||
android:summary="Tweaks and Workarounds"
|
||||
android:theme="@android:style/Theme.Light.WallpaperSettings">
|
||||
<intent android:action="android.intent.action.VIEW"
|
||||
android:targetPackage="com.derpfish.pinkielive"
|
||||
android:targetClass="com.derpfish.pinkielive.preference.PinkiePieLiveWallpaperSettingsAdvanced"/>
|
||||
</PreferenceScreen>
|
||||
<com.derpfish.pinkielive.preference.CreditsPreference
|
||||
android:title="Credits" />
|
||||
</PreferenceScreen>
|
17
res/xml/livewallpaper_settings_advanced.xml
Normal file
17
res/xml/livewallpaper_settings_advanced.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="Advanced Settings"
|
||||
android:key="livewallpaper_settings_advanced">
|
||||
|
||||
<EditTextPreference android:key="livewallpaper_framerate"
|
||||
android:title="Framerate"
|
||||
android:summary="Frames per second for pony animations"
|
||||
android:numeric="integer"
|
||||
android:defaultValue="60" />
|
||||
<CheckBoxPreference android:key="livewallpaper_enableparallax"
|
||||
android:defaultValue="true"
|
||||
android:title="Enable Parallax Scrolling"
|
||||
android:summaryOn="Parallax Scrolling Enabled"
|
||||
android:summaryOff="Parallax Scrolling Disabled" />
|
||||
|
||||
</PreferenceScreen>
|
39
src/com/derpfish/pinkielive/BitmapLoader.java
Normal file
39
src/com/derpfish/pinkielive/BitmapLoader.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.derpfish.pinkielive;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
public class BitmapLoader
|
||||
{
|
||||
public static Bitmap decodeSampledBitmapFromInputStream(InputStream istream, int sampleSize)
|
||||
{
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = sampleSize;
|
||||
return BitmapFactory.decodeStream(istream, null, options);
|
||||
}
|
||||
|
||||
public static int getSampleSizeFromInputStream(InputStream istream, int reqWidth, int reqHeight)
|
||||
{
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(istream, null, options);
|
||||
|
||||
// Calculate inSampleSize
|
||||
return calculateInSampleSize(options, reqWidth, reqHeight);
|
||||
}
|
||||
|
||||
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
|
||||
{
|
||||
// Raw height and width of image
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
|
||||
return Math.max(1, Math.max(
|
||||
Math.round((float) height / (float) reqHeight),
|
||||
Math.round((float) width / (float) reqWidth)));
|
||||
}
|
||||
}
|
425
src/com/derpfish/pinkielive/PinkiePieLiveWallpaper.java
Normal file
425
src/com/derpfish/pinkielive/PinkiePieLiveWallpaper.java
Normal file
@ -0,0 +1,425 @@
|
||||
package com.derpfish.pinkielive;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.derpfish.pinkielive.animation.PinkieAnimation;
|
||||
import com.derpfish.pinkielive.animation.PonyAnimation;
|
||||
import com.derpfish.pinkielive.download.PonyAnimationContainer;
|
||||
import com.derpfish.pinkielive.download.PonyDownloader;
|
||||
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.service.wallpaper.WallpaperService;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
public class PinkiePieLiveWallpaper extends WallpaperService
|
||||
{
|
||||
public static final String SHARED_PREFS_NAME = "livewallpapersettings";
|
||||
// Round-trip time for a jump to complete in ms.
|
||||
public static final double TIME_FOR_JUMP = 1500.0;
|
||||
|
||||
private Bitmap defaultBg = null;
|
||||
|
||||
// Settings
|
||||
private Bitmap selectedBg = null;
|
||||
private boolean useDefaultBg = true;
|
||||
private long targetFramerate = 60L;
|
||||
private boolean enableParallax = true;
|
||||
private PonyAnimation selectedPony;
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
if (defaultBg != null)
|
||||
{
|
||||
defaultBg.recycle();
|
||||
defaultBg = null;
|
||||
}
|
||||
if (selectedBg != null)
|
||||
{
|
||||
selectedBg.recycle();
|
||||
selectedBg = null;
|
||||
}
|
||||
if (selectedPony != null)
|
||||
{
|
||||
selectedPony.onDestroy();
|
||||
selectedPony = null;
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine onCreateEngine()
|
||||
{
|
||||
return new PonyEngine();
|
||||
}
|
||||
|
||||
class PonyEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
private long lastUpdate;
|
||||
|
||||
private int surfaceWidth;
|
||||
private int surfaceHeight;
|
||||
private float offsetX;
|
||||
private float offsetY;
|
||||
private final Handler mHandler = new Handler();
|
||||
private final Paint mPaint = new Paint();
|
||||
private final Runnable mDrawPattern = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
drawFrame();
|
||||
}
|
||||
};
|
||||
|
||||
private boolean mVisible;
|
||||
private final SharedPreferences mPreferences;
|
||||
private final BroadcastReceiver broadcastReceiver;
|
||||
|
||||
private PonyEngine()
|
||||
{
|
||||
final Paint paint = mPaint;
|
||||
paint.setColor(0xffffffff);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(2);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mPreferences = PinkiePieLiveWallpaper.this.getSharedPreferences(SHARED_PREFS_NAME, 0);
|
||||
mPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
/*
|
||||
* If the media scanner finishes a scan, reload the preferences
|
||||
* since this means a previously unavailable background is now
|
||||
* available.
|
||||
*/
|
||||
final IntentFilter iFilter = new IntentFilter();
|
||||
iFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
|
||||
iFilter.addDataScheme("file");
|
||||
|
||||
broadcastReceiver = new BroadcastReceiver()
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
final String action = intent.getAction();
|
||||
if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED))
|
||||
{
|
||||
onSharedPreferenceChanged(mPreferences, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
registerReceiver(broadcastReceiver, iFilter);
|
||||
|
||||
// Load saved preferences
|
||||
onSharedPreferenceChanged(mPreferences, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
|
||||
{
|
||||
useDefaultBg = prefs.getBoolean("livewallpaper_defaultbg", true);
|
||||
if (useDefaultBg)
|
||||
{
|
||||
if (selectedBg != null)
|
||||
{
|
||||
selectedBg.recycle();
|
||||
selectedBg = null;
|
||||
}
|
||||
try
|
||||
{
|
||||
final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
|
||||
final AssetManager assetManager = getAssets();
|
||||
|
||||
InputStream istr = assetManager.open("defaultbg.jpg");
|
||||
final int sampleSize = BitmapLoader.getSampleSizeFromInputStream(istr,
|
||||
wmMan.getDesiredMinimumWidth(), wmMan.getDesiredMinimumHeight());
|
||||
istr.close();
|
||||
istr = assetManager.open("defaultbg.jpg");
|
||||
defaultBg = BitmapLoader.decodeSampledBitmapFromInputStream(istr, sampleSize);
|
||||
istr.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IllegalStateException("Could not find background image");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (defaultBg != null)
|
||||
{
|
||||
defaultBg.recycle();
|
||||
defaultBg = null;
|
||||
}
|
||||
|
||||
final String imageUriStr = prefs.getString("livewallpaper_image", null);
|
||||
if (imageUriStr != null)
|
||||
{
|
||||
if (selectedBg != null)
|
||||
{
|
||||
selectedBg.recycle();
|
||||
}
|
||||
|
||||
final Uri bgImage = Uri.parse(imageUriStr);
|
||||
try
|
||||
{
|
||||
final ContentResolver contentResolver = getContentResolver();
|
||||
final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
|
||||
|
||||
InputStream istr = contentResolver.openInputStream(bgImage);
|
||||
final int sampleSize = BitmapLoader.getSampleSizeFromInputStream(istr,
|
||||
wmMan.getDesiredMinimumWidth(), wmMan.getDesiredMinimumHeight());
|
||||
istr.close();
|
||||
|
||||
istr = contentResolver.openInputStream(bgImage);
|
||||
selectedBg = BitmapLoader.decodeSampledBitmapFromInputStream(istr, sampleSize);
|
||||
istr.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
selectedBg = null;
|
||||
Log.w("PinkieLive", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Framerate
|
||||
final String frameratePref = prefs.getString("livewallpaper_framerate", "60");
|
||||
try
|
||||
{
|
||||
targetFramerate = Long.parseLong(frameratePref);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
Log.e("PinkieLive", e.getMessage());
|
||||
}
|
||||
|
||||
// Parallax
|
||||
enableParallax = prefs.getBoolean("livewallpaper_enableparallax", true);
|
||||
if (!enableParallax)
|
||||
{
|
||||
offsetX = offsetY = 0.0f;
|
||||
}
|
||||
|
||||
// Change selected pony
|
||||
if (selectedPony != null)
|
||||
{
|
||||
selectedPony.onDestroy();
|
||||
}
|
||||
// Refresh pony animations; preference change could have updated them
|
||||
final Map<String, PonyAnimation> ponyAnimations = new HashMap<String, PonyAnimation>();
|
||||
ponyAnimations.put("pinkie", new PinkieAnimation(getAssets()));
|
||||
try
|
||||
{
|
||||
for (final PonyAnimationContainer container : PonyDownloader.getPonyAnimations(
|
||||
getFilesDir(), getCacheDir(), true))
|
||||
{
|
||||
ponyAnimations.put(container.getId(), container.getPonyAnimation());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// Set new pony animation
|
||||
final String selectedPonyId = prefs.getString("livewallpaper_pony", "pinkie");
|
||||
selectedPony = ponyAnimations.containsKey(selectedPonyId) ? ponyAnimations.get(selectedPonyId) : ponyAnimations.get("pinkie");
|
||||
selectedPony.onCreate();
|
||||
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SurfaceHolder surfaceHolder)
|
||||
{
|
||||
super.onCreate(surfaceHolder);
|
||||
setTouchEventsEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
|
||||
mHandler.removeCallbacks(mDrawPattern);
|
||||
unregisterReceiver(broadcastReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVisibilityChanged(boolean visible)
|
||||
{
|
||||
mVisible = visible;
|
||||
if (visible)
|
||||
{
|
||||
drawFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
mHandler.removeCallbacks(mDrawPattern);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)
|
||||
{
|
||||
super.onSurfaceChanged(holder, format, width, height);
|
||||
surfaceWidth = width;
|
||||
surfaceHeight = height;
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(SurfaceHolder holder)
|
||||
{
|
||||
super.onSurfaceCreated(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceDestroyed(SurfaceHolder holder)
|
||||
{
|
||||
super.onSurfaceDestroyed(holder);
|
||||
mVisible = false;
|
||||
mHandler.removeCallbacks(mDrawPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels)
|
||||
{
|
||||
if (enableParallax)
|
||||
{
|
||||
offsetX = xPixels;
|
||||
offsetY = yPixels;
|
||||
drawFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Store the position of the touch event so we can use it for drawing
|
||||
* later
|
||||
*/
|
||||
@Override
|
||||
public void onTouchEvent(MotionEvent event)
|
||||
{
|
||||
if (event.getAction() == MotionEvent.ACTION_UP)
|
||||
{
|
||||
// If the length of time pressed was less than 0.5 seconds,
|
||||
// trigger a new drawing
|
||||
if (event.getEventTime() - event.getDownTime() < 500)
|
||||
{
|
||||
if (selectedPony.isComplete())
|
||||
{
|
||||
selectedPony.initialize(surfaceWidth, surfaceHeight, event.getX(), event.getY());
|
||||
lastUpdate = SystemClock.elapsedRealtime();
|
||||
drawFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw one frame of the animation. This method gets called repeatedly
|
||||
* by posting a delayed Runnable. You can do any drawing you want in
|
||||
* here. This example draws a wireframe cube.
|
||||
*/
|
||||
private void drawFrame()
|
||||
{
|
||||
final SurfaceHolder holder = getSurfaceHolder();
|
||||
|
||||
Canvas c = null;
|
||||
try
|
||||
{
|
||||
c = holder.lockCanvas();
|
||||
if (c != null)
|
||||
{
|
||||
// Blank canvas
|
||||
final Paint paintBlack = new Paint();
|
||||
paintBlack.setColor(0xff000000);
|
||||
c.drawRect(0.0f, 0.0f, surfaceWidth, surfaceHeight, paintBlack);
|
||||
|
||||
// draw something
|
||||
if (useDefaultBg || selectedBg != null)
|
||||
{
|
||||
final Bitmap actualBg = selectedBg != null ? selectedBg : defaultBg;
|
||||
|
||||
if (enableParallax)
|
||||
{
|
||||
final WallpaperManager wmMan = WallpaperManager.getInstance(getApplicationContext());
|
||||
final int minWidth = wmMan.getDesiredMinimumWidth();
|
||||
final int minHeight = wmMan.getDesiredMinimumHeight();
|
||||
final float bgScale = Math.min(((float)actualBg.getWidth()) / ((float)minWidth), ((float)actualBg.getHeight()) / ((float)minHeight));
|
||||
final int centeringOffsetX = (int)((float)actualBg.getWidth() - bgScale*minWidth)/2;
|
||||
final int centeringOffsetY = (int)((float)actualBg.getHeight() - bgScale*minHeight)/2;
|
||||
|
||||
c.drawBitmap(actualBg,
|
||||
new Rect(centeringOffsetX - (int)(offsetX*bgScale),
|
||||
centeringOffsetY - (int)(offsetY*bgScale),
|
||||
centeringOffsetX + (int)((-offsetX + surfaceWidth)*bgScale),
|
||||
centeringOffsetY + (int)((-offsetY + surfaceHeight)*bgScale)),
|
||||
new Rect(0, 0, surfaceWidth, surfaceHeight), mPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
final float bgScale = Math.min(((float)actualBg.getWidth()) / ((float)surfaceWidth), ((float)actualBg.getHeight()) / ((float)surfaceHeight));
|
||||
final int centeringOffsetX = (int)((float)actualBg.getWidth() - bgScale*surfaceWidth)/2;
|
||||
final int centeringOffsetY = (int)((float)actualBg.getHeight() - bgScale*surfaceHeight)/2;
|
||||
c.drawBitmap(actualBg,
|
||||
new Rect(centeringOffsetX, centeringOffsetY,
|
||||
centeringOffsetX + (int)(surfaceWidth*bgScale),
|
||||
centeringOffsetY + (int)(surfaceHeight*bgScale)),
|
||||
new Rect(0, 0, surfaceWidth, surfaceHeight), mPaint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Decide new position and velocity.
|
||||
if (!selectedPony.isComplete())
|
||||
{
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
long elapsedTime = now - lastUpdate;
|
||||
lastUpdate = now;
|
||||
selectedPony.drawAnimation(c, elapsedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null)
|
||||
holder.unlockCanvasAndPost(c);
|
||||
}
|
||||
|
||||
mHandler.removeCallbacks(mDrawPattern);
|
||||
// Only queue another frame if we're still animating pinkie
|
||||
if (mVisible && !selectedPony.isComplete())
|
||||
{
|
||||
mHandler.postDelayed(mDrawPattern, 1000 / targetFramerate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
212
src/com/derpfish/pinkielive/animation/PinkieAnimation.java
Normal file
212
src/com/derpfish/pinkielive/animation/PinkieAnimation.java
Normal file
@ -0,0 +1,212 @@
|
||||
package com.derpfish.pinkielive.animation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
|
||||
public class PinkieAnimation implements PonyAnimation
|
||||
{
|
||||
private float surfaceWidth;
|
||||
private float surfaceHeight;
|
||||
|
||||
private float pinkieX;
|
||||
private double pinkieVelocityX;
|
||||
private float pinkieRotationAngle;
|
||||
private float pinkieTargetHeight;
|
||||
private boolean flipAnimation;
|
||||
|
||||
private boolean completed = true;
|
||||
private long accumulatedTime;
|
||||
|
||||
private Bitmap[] bmAnimation = null;
|
||||
private int lastAnim = -1;
|
||||
|
||||
private final Paint mPaint = new Paint();
|
||||
|
||||
private final AssetManager assetManager;
|
||||
private static final int NUM_ANIMATIONS = 7;
|
||||
private static final long FRAME_DELAY = 50;
|
||||
|
||||
private Thread loaderThread;
|
||||
private final LoadAnimationRunnable loadAnimationRunnable = new LoadAnimationRunnable();
|
||||
|
||||
public PinkieAnimation(final AssetManager assetManager)
|
||||
{
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(int surfaceWidth, int surfaceHeight, float tapX, float tapY)
|
||||
{
|
||||
this.surfaceWidth = surfaceWidth;
|
||||
this.surfaceHeight = surfaceHeight;
|
||||
|
||||
// Wait for animation to be fully loaded
|
||||
waitForLoader();
|
||||
|
||||
completed = false;
|
||||
accumulatedTime = 0L;
|
||||
|
||||
// Target getting 2/3 of the way up the image to this position
|
||||
final int PINKIE_WIDTH = (int) Math.min(surfaceWidth * 0.75, surfaceHeight * 0.75);
|
||||
final float scale = (float) PINKIE_WIDTH / (float) bmAnimation[0].getWidth();
|
||||
pinkieTargetHeight = Math.min(surfaceHeight / 2, tapY) - scale * (bmAnimation[0].getHeight() / 3.0f);
|
||||
|
||||
final double pinkieVelocityY = 4.0 * (pinkieTargetHeight - surfaceHeight) / ((double) (bmAnimation.length * FRAME_DELAY));
|
||||
pinkieX = (surfaceWidth - tapX);
|
||||
pinkieVelocityX = 2.0 * (surfaceWidth / 2.0f - pinkieX) / ((double) (bmAnimation.length * FRAME_DELAY));
|
||||
flipAnimation = (pinkieVelocityX < 0);
|
||||
|
||||
pinkieRotationAngle = (float) Math.toDegrees(Math.atan2(pinkieVelocityX, -pinkieVelocityY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawAnimation(final Canvas canvas, final long elapsedTime)
|
||||
{
|
||||
// Decide new position and velocity.
|
||||
if (!completed)
|
||||
{
|
||||
accumulatedTime += elapsedTime;
|
||||
final int currentFrame = (int) (accumulatedTime / FRAME_DELAY);
|
||||
|
||||
final double pinkieY = surfaceHeight + (pinkieTargetHeight - surfaceHeight) * (1 - Math.pow((2.0 * accumulatedTime) / ((double) (bmAnimation.length * FRAME_DELAY)) - 1.0, 4.0));
|
||||
pinkieX = (float) (elapsedTime * pinkieVelocityX + pinkieX);
|
||||
|
||||
if (currentFrame < bmAnimation.length && pinkieY <= surfaceHeight)
|
||||
{
|
||||
final Bitmap bitmap = bmAnimation[currentFrame];
|
||||
final int PINKIE_WIDTH = (int) Math.min(surfaceWidth * 0.75, surfaceHeight * 0.75);
|
||||
final float scale = (float) PINKIE_WIDTH / (float) bitmap.getWidth();
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
if (flipAnimation)
|
||||
{
|
||||
matrix.postScale(-1.0f, 1.0f);
|
||||
matrix.postTranslate(bitmap.getWidth(), 0.0f);
|
||||
}
|
||||
matrix.postRotate(pinkieRotationAngle, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
|
||||
matrix.postScale(scale, scale);
|
||||
matrix.postTranslate(pinkieX - PINKIE_WIDTH / 2, (float) pinkieY);
|
||||
canvas.drawBitmap(bitmap, matrix, mPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
completed = true;
|
||||
loadNextAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNextAnimation()
|
||||
{
|
||||
waitForLoader();
|
||||
loaderThread = new Thread(loadAnimationRunnable);
|
||||
loaderThread.start();
|
||||
}
|
||||
|
||||
private void waitForLoader()
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (loaderThread == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
loaderThread.join();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new IllegalStateException("Unhandled interruption: " + e);
|
||||
}
|
||||
loaderThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadAnimationRunnable implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
lastAnim = (lastAnim + 1) % NUM_ANIMATIONS;
|
||||
if (bmAnimation != null)
|
||||
{
|
||||
for (int i = 0; i < bmAnimation.length; i++)
|
||||
{
|
||||
bmAnimation[i].recycle();
|
||||
}
|
||||
bmAnimation = null;
|
||||
}
|
||||
try
|
||||
{
|
||||
final InputStream istr = assetManager.open("jump" + (lastAnim + 1) + ".zip");
|
||||
final ZipInputStream zis = new ZipInputStream(istr);
|
||||
final Map<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
|
||||
ZipEntry zipEntry = null;
|
||||
while ((zipEntry = zis.getNextEntry()) != null)
|
||||
{
|
||||
bitmaps.put(zipEntry.getName(), BitmapFactory.decodeStream(zis));
|
||||
}
|
||||
zis.close();
|
||||
istr.close();
|
||||
|
||||
final List<String> names = new ArrayList<String>(bitmaps.keySet());
|
||||
Collections.sort(names);
|
||||
bmAnimation = new Bitmap[bitmaps.size()];
|
||||
for (int i = 0; i < names.size(); i++)
|
||||
{
|
||||
bmAnimation[i] = bitmaps.get(names.get(i));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IllegalStateException("Could not load animation: " + lastAnim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete()
|
||||
{
|
||||
return completed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
completed = true;
|
||||
loadNextAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
if (bmAnimation != null)
|
||||
{
|
||||
for (int i = 0; i < bmAnimation.length; i++)
|
||||
{
|
||||
bmAnimation[i].recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceDir(File resourceDir)
|
||||
{
|
||||
}
|
||||
}
|
21
src/com/derpfish/pinkielive/animation/PonyAnimation.java
Normal file
21
src/com/derpfish/pinkielive/animation/PonyAnimation.java
Normal file
@ -0,0 +1,21 @@
|
||||
package com.derpfish.pinkielive.animation;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
|
||||
public interface PonyAnimation
|
||||
{
|
||||
public void setResourceDir(final File resourceDir);
|
||||
|
||||
public void initialize(int surfaceWidth, int surfaceHeight, float tapX, float tapY);
|
||||
|
||||
public void drawAnimation(Canvas canvas, long elapsedTime);
|
||||
|
||||
public boolean isComplete();
|
||||
|
||||
public void onCreate();
|
||||
|
||||
public void onDestroy();
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.derpfish.pinkielive.download;
|
||||
|
||||
import com.derpfish.pinkielive.animation.PonyAnimation;
|
||||
|
||||
public class PonyAnimationContainer
|
||||
{
|
||||
private String id;
|
||||
private String name;
|
||||
private Long version;
|
||||
private PonyAnimation ponyAnimation;
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public Long getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public PonyAnimation getPonyAnimation()
|
||||
{
|
||||
return ponyAnimation;
|
||||
}
|
||||
|
||||
public void setPonyAnimation(PonyAnimation ponyAnimation)
|
||||
{
|
||||
this.ponyAnimation = ponyAnimation;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.derpfish.pinkielive.download;
|
||||
|
||||
public class PonyAnimationListing
|
||||
{
|
||||
private String name;
|
||||
private String url;
|
||||
private String id;
|
||||
private Long version;
|
||||
private String checksum;
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
public String getUrl()
|
||||
{
|
||||
return url;
|
||||
}
|
||||
public void setUrl(String url)
|
||||
{
|
||||
this.url = url;
|
||||
}
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
public void setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
public Long getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
public void setVersion(Long version)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
public String getChecksum()
|
||||
{
|
||||
return checksum;
|
||||
}
|
||||
public void setChecksum(String checksum)
|
||||
{
|
||||
this.checksum = checksum;
|
||||
}
|
||||
}
|
333
src/com/derpfish/pinkielive/download/PonyDownloader.java
Normal file
333
src/com/derpfish/pinkielive/download/PonyDownloader.java
Normal file
@ -0,0 +1,333 @@
|
||||
package com.derpfish.pinkielive.download;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.derpfish.pinkielive.animation.PonyAnimation;
|
||||
import com.derpfish.pinkielive.util.Base64;
|
||||
import com.derpfish.pinkielive.util.IOUtils;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class PonyDownloader
|
||||
{
|
||||
private static final String animationsUrl = "http://animations.pinkie-live.googlecode.com/git/animations.xml";
|
||||
private static final String animationsSigUrl = "http://animations.pinkie-live.googlecode.com/git/animations.sig";
|
||||
|
||||
private static final String PUBLIC_KEY_ENCODED =
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1aP3qdJO5F64+UQVF1zl" +
|
||||
"DyyM/bN/jJZgU9guq7RLLO81y1IFpxL0Rs66FMkcSz4ZtdtOT6uRhZ9TvrVhMR69" +
|
||||
"WpxdH3hrh4JhILd+/ZRxQt2GX4FyLDIPqfpA867ZjS+lrgncN48kC2X3z3ETQ46Q" +
|
||||
"eCjYrLfKeseGy620dWuIV2yXenr1NHSJ5kwOKvOdddEHwijSNwpDo8C93XGCAHtT" +
|
||||
"fXcmknBeVPNWC1iL0CshpMWuDIvLEh867J6HvpSzyAss0q62mvRyttifjZO8aiSH" +
|
||||
"LctTLxMnTrxhL9mw4lmFCzI0UoWmeSOiEOQTXhGxWhVE9gXv0jzizTvX5DG9MtTA" +
|
||||
"CwIDAQAB";
|
||||
|
||||
private static Object lock = new Object();
|
||||
private static PublicKey publicKey = null;
|
||||
|
||||
public static boolean verifyData(final InputStream istream, final String base64sig)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException, InvalidKeySpecException
|
||||
{
|
||||
synchronized(lock)
|
||||
{
|
||||
if (publicKey == null)
|
||||
{
|
||||
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY_ENCODED, Base64.DEFAULT));
|
||||
final KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||
publicKey = kf.generatePublic(keySpec);
|
||||
}
|
||||
}
|
||||
|
||||
final Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initVerify(publicKey);
|
||||
|
||||
final byte[] buffer = new byte[4096];
|
||||
int nread = 0;
|
||||
while ((nread = istream.read(buffer)) >= 0)
|
||||
{
|
||||
signature.update(buffer, 0, nread);
|
||||
}
|
||||
istream.close();
|
||||
|
||||
return signature.verify(Base64.decode(base64sig, Base64.DEFAULT));
|
||||
}
|
||||
|
||||
public static List<PonyAnimationListing> fetchListings() throws ParserConfigurationException, SAXException, IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, InvalidKeySpecException
|
||||
{
|
||||
final byte[] xmlBytes = fetchUrl(animationsUrl, 131072);
|
||||
final byte[] sigBytes = fetchUrl(animationsSigUrl, 4096);
|
||||
if (!verifyData(new ByteArrayInputStream(xmlBytes), new String(sigBytes)))
|
||||
{
|
||||
throw new IllegalStateException("Unable to verify signature of animations.xml");
|
||||
}
|
||||
|
||||
return parseListings(new ByteArrayInputStream(xmlBytes));
|
||||
}
|
||||
|
||||
private static byte[] fetchUrl(final String url, int maxSize) throws MalformedURLException, IOException
|
||||
{
|
||||
final URLConnection connection = new URL(url).openConnection();
|
||||
connection.connect();
|
||||
|
||||
final InputStream istream = connection.getInputStream();
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int totalRead = 0;
|
||||
int nRead = 0;
|
||||
final byte[] buffer = new byte[4096];
|
||||
while ((nRead = istream.read(buffer)) >= 0)
|
||||
{
|
||||
baos.write(buffer, 0, nRead);
|
||||
totalRead += nRead;
|
||||
if (maxSize > 0 && totalRead > maxSize)
|
||||
{
|
||||
throw new IllegalStateException("Retrieved animations.xml exceeds maximum allowed size.");
|
||||
}
|
||||
}
|
||||
istream.close();
|
||||
baos.close();
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static List<PonyAnimationListing> parseListings(final InputStream istream) throws ParserConfigurationException, SAXException, IOException
|
||||
{
|
||||
final List<PonyAnimationListing> animationListings = new ArrayList<PonyAnimationListing>();
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
final DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
final Document dom = builder.parse(istream);
|
||||
istream.close();
|
||||
|
||||
final Element root = dom.getDocumentElement();
|
||||
if (!root.getNodeName().equals("animations"))
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
|
||||
for (int i = 0; i < root.getChildNodes().getLength(); i++)
|
||||
{
|
||||
final Node animation = root.getChildNodes().item(i);
|
||||
if (animation.getNodeType() == Node.TEXT_NODE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!animation.getNodeName().equals("animation"))
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
|
||||
final PonyAnimationListing animListing = new PonyAnimationListing();
|
||||
for (int j = 0; j < animation.getChildNodes().getLength(); j++)
|
||||
{
|
||||
final Node attr = animation.getChildNodes().item(j);
|
||||
if (attr.getNodeType() == Node.TEXT_NODE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attr.getNodeName().equals("name"))
|
||||
{
|
||||
if (animListing.getName() != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
animListing.setName(getNodeText(attr));
|
||||
}
|
||||
else if (attr.getNodeName().equals("url"))
|
||||
{
|
||||
if (animListing.getUrl() != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
animListing.setUrl(getNodeText(attr));
|
||||
}
|
||||
else if (attr.getNodeName().equals("id"))
|
||||
{
|
||||
if (animListing.getId() != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
animListing.setId(getNodeText(attr));
|
||||
}
|
||||
else if (attr.getNodeName().equals("version"))
|
||||
{
|
||||
if (animListing.getVersion() != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
animListing.setVersion(Long.parseLong(getNodeText(attr)));
|
||||
}
|
||||
else if (attr.getNodeName().equals("checksum"))
|
||||
{
|
||||
if (animListing.getChecksum() != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
animListing.setChecksum(getNodeText(attr));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
}
|
||||
|
||||
if (animListing.getName() == null || animListing.getUrl() == null
|
||||
|| animListing.getId() == null || animListing.getVersion() == null
|
||||
|| animListing.getChecksum() == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
|
||||
animationListings.add(animListing);
|
||||
}
|
||||
|
||||
return animationListings;
|
||||
}
|
||||
|
||||
private static String getNodeText(final Node node)
|
||||
{
|
||||
if (node.getChildNodes().getLength() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
final Node innerNode = node.getChildNodes().item(0);
|
||||
if (innerNode.getNodeType() != Node.TEXT_NODE)
|
||||
{
|
||||
throw new IllegalArgumentException("Malformed XML");
|
||||
}
|
||||
return innerNode.getNodeValue();
|
||||
}
|
||||
|
||||
public static void fetchPony(final File dataDir, final File cacheDir, final PonyAnimationListing animation) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, InvalidKeySpecException
|
||||
{
|
||||
final File ponyDir = new File(dataDir.getAbsolutePath() + File.separator + "ponies");
|
||||
if (!ponyDir.exists())
|
||||
{
|
||||
ponyDir.mkdir();
|
||||
}
|
||||
final File tmpFile = new File(cacheDir.getAbsolutePath() + File.separator + "delme.zip");
|
||||
|
||||
final URLConnection connection = new URL(animation.getUrl()).openConnection();
|
||||
connection.connect();
|
||||
IOUtils.copyStreamAndClose(connection.getInputStream(), new FileOutputStream(tmpFile));
|
||||
|
||||
if (!verifyData(new FileInputStream(tmpFile), animation.getChecksum()))
|
||||
{
|
||||
throw new IllegalStateException("Signature verification failed.");
|
||||
}
|
||||
|
||||
final File animDir = new File(ponyDir.getAbsolutePath() + File.separator + animation.getId());
|
||||
if (animDir.exists())
|
||||
{
|
||||
for (File file : animDir.listFiles())
|
||||
{
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
animDir.mkdir();
|
||||
}
|
||||
|
||||
final InputStream istr = new FileInputStream(tmpFile);
|
||||
final ZipInputStream zis = new ZipInputStream(istr);
|
||||
ZipEntry zipEntry = null;
|
||||
while ((zipEntry = zis.getNextEntry()) != null)
|
||||
{
|
||||
final FileOutputStream fos = new FileOutputStream(animDir.getAbsolutePath() + File.separator + zipEntry.getName());
|
||||
IOUtils.copyStream(zis, fos);
|
||||
fos.close();
|
||||
}
|
||||
zis.close();
|
||||
istr.close();
|
||||
|
||||
tmpFile.delete();
|
||||
}
|
||||
|
||||
public static List<PonyAnimationContainer> getPonyAnimations(final File dataDir, final File cacheDir,
|
||||
final boolean loadAnimations)
|
||||
throws FileNotFoundException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException
|
||||
{
|
||||
final List<PonyAnimationContainer> containers = new ArrayList<PonyAnimationContainer>();
|
||||
final File ponyDir = new File(dataDir.getAbsolutePath() + File.separator + "ponies");
|
||||
if (!ponyDir.exists())
|
||||
{
|
||||
return containers;
|
||||
}
|
||||
|
||||
for (final File subDir : ponyDir.listFiles())
|
||||
{
|
||||
if (subDir.isDirectory())
|
||||
{
|
||||
final File manifest = new File(subDir.getAbsolutePath() + File.separator + "manifest.properties");
|
||||
if (!manifest.exists())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
final File lib = new File(subDir.getAbsolutePath() + File.separator + "lib.jar");
|
||||
if (!lib.exists())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final Properties properties = new Properties();
|
||||
properties.load(new FileInputStream(manifest));
|
||||
|
||||
final PonyAnimationContainer container = new PonyAnimationContainer();
|
||||
container.setId(properties.getProperty("id"));
|
||||
container.setName(properties.getProperty("name"));
|
||||
container.setVersion(Long.parseLong(properties.getProperty("version")));
|
||||
|
||||
if (loadAnimations)
|
||||
{
|
||||
final File animCacheDir = new File(cacheDir.getAbsolutePath() + File.separator + container.getId());
|
||||
if (!animCacheDir.exists())
|
||||
{
|
||||
animCacheDir.mkdir();
|
||||
}
|
||||
|
||||
final DexClassLoader classLoader = new DexClassLoader(lib.getAbsolutePath(),
|
||||
animCacheDir.getAbsolutePath(), null, PonyDownloader.class.getClassLoader());
|
||||
Class<?> animClass = classLoader.loadClass(properties.getProperty("className"));
|
||||
container.setPonyAnimation(animClass.asSubclass(PonyAnimation.class).newInstance());
|
||||
container.getPonyAnimation().setResourceDir(subDir);
|
||||
}
|
||||
|
||||
containers.add(container);
|
||||
}
|
||||
}
|
||||
return containers;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.derpfish.pinkielive.preference;
|
||||
|
||||
import com.derpfish.pinkielive.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.preference.DialogPreference;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class CreditsPreference extends DialogPreference
|
||||
{
|
||||
private View view = null;
|
||||
|
||||
public CreditsPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public CreditsPreference(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize()
|
||||
{
|
||||
this.setWidgetLayoutResource(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareDialogBuilder(final AlertDialog.Builder builder)
|
||||
{
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
final TextView textView = new TextView(getContext());
|
||||
textView.setBackgroundColor(Color.WHITE);
|
||||
textView.setTextSize(18.0f);
|
||||
textView.setText(Html.fromHtml(getContext().getString(R.string.credits_string)));
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
final ScrollView scrollView = new ScrollView(getContext());
|
||||
scrollView.setFillViewport(true);
|
||||
scrollView.addView(textView);
|
||||
|
||||
view = scrollView;
|
||||
}
|
||||
|
||||
builder.setView(view);
|
||||
builder.setIcon(0);
|
||||
builder.setPositiveButton(null, null);
|
||||
builder.setNegativeButton(null, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.derpfish.pinkielive.preference;
|
||||
|
||||
import com.derpfish.pinkielive.PinkiePieLiveWallpaper;
|
||||
import com.derpfish.pinkielive.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
|
||||
public class PinkiePieLiveWallpaperSettings extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, OnPreferenceClickListener
|
||||
{
|
||||
private static final int SELECT_PICTURE = 1;
|
||||
|
||||
private Intent gallery;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle)
|
||||
{
|
||||
super.onCreate(icicle);
|
||||
|
||||
getPreferenceManager().setSharedPreferencesName(PinkiePieLiveWallpaper.SHARED_PREFS_NAME);
|
||||
addPreferencesFromResource(R.xml.livewallpaper_settings);
|
||||
|
||||
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
setDefaultBgEnabled();
|
||||
|
||||
findPreference("livewallpaper_image").setOnPreferenceClickListener(this);
|
||||
|
||||
gallery = new Intent();
|
||||
gallery.setType("image/*");
|
||||
gallery.setAction(Intent.ACTION_GET_CONTENT);
|
||||
}
|
||||
|
||||
private void setDefaultBgEnabled()
|
||||
{
|
||||
final SharedPreferences sharedPr = getPreferenceManager().getSharedPreferences();
|
||||
findPreference("livewallpaper_defaultbg").setEnabled(
|
||||
!sharedPr.getBoolean("livewallpaper_defaultbg", true)
|
||||
|| sharedPr.getString("livewallpaper_image", null) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy()
|
||||
{
|
||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
|
||||
{
|
||||
setDefaultBgEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference pr)
|
||||
{
|
||||
if (pr.getKey().equals("livewallpaper_image"))
|
||||
{
|
||||
startActivityForResult(Intent.createChooser(gallery, "Select Background"), SELECT_PICTURE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
if (resultCode == RESULT_OK)
|
||||
{
|
||||
if (requestCode == SELECT_PICTURE)
|
||||
{
|
||||
Uri selectedImageUri = data.getData();
|
||||
SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit();
|
||||
editor.putString("livewallpaper_image", selectedImageUri.toString());
|
||||
editor.putBoolean("livewallpaper_defaultbg", false);
|
||||
editor.commit();
|
||||
((CheckBoxPreference) findPreference("livewallpaper_defaultbg")).setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.derpfish.pinkielive.preference;
|
||||
|
||||
import com.derpfish.pinkielive.PinkiePieLiveWallpaper;
|
||||
import com.derpfish.pinkielive.R;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
|
||||
public class PinkiePieLiveWallpaperSettingsAdvanced extends PreferenceActivity
|
||||
{
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle)
|
||||
{
|
||||
super.onCreate(icicle);
|
||||
|
||||
getPreferenceManager().setSharedPreferencesName(PinkiePieLiveWallpaper.SHARED_PREFS_NAME);
|
||||
addPreferencesFromResource(R.xml.livewallpaper_settings_advanced);
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package com.derpfish.pinkielive.preference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.derpfish.pinkielive.R;
|
||||
import com.derpfish.pinkielive.download.PonyAnimationContainer;
|
||||
import com.derpfish.pinkielive.download.PonyAnimationListing;
|
||||
import com.derpfish.pinkielive.download.PonyDownloader;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class PonyDownloadListPreference extends DialogPreference implements View.OnClickListener
|
||||
{
|
||||
public PonyDownloadListPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PonyDownloadListPreference(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
private Map<String, PonyAnimationContainer> currentPonies = new HashMap<String, PonyAnimationContainer>();
|
||||
private List<PonyAnimationListing> newListings = new ArrayList<PonyAnimationListing>();
|
||||
|
||||
@Override
|
||||
protected void onClick()
|
||||
{
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
private ProgressDialog dialog;
|
||||
private List<PonyAnimationContainer> currentPonies;
|
||||
private List<PonyAnimationListing> animListings;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
dialog = ProgressDialog.show(getContext(), "", "Loading, please wait...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentPonies = PonyDownloader.getPonyAnimations(
|
||||
getContext().getFilesDir(), getContext().getCacheDir(), false);
|
||||
animListings = PonyDownloader.fetchListings();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result)
|
||||
{
|
||||
dialog.dismiss();
|
||||
doRealOnclick(currentPonies, animListings);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void doRealOnclick(final List<PonyAnimationContainer> currentPonies, final List<PonyAnimationListing> animListings)
|
||||
{
|
||||
this.currentPonies.clear();
|
||||
for (final PonyAnimationContainer pac : currentPonies)
|
||||
{
|
||||
this.currentPonies.put(pac.getId(), pac);
|
||||
}
|
||||
newListings.clear();
|
||||
for (final PonyAnimationListing pal : animListings)
|
||||
{
|
||||
if (!this.currentPonies.containsKey(pal.getId())
|
||||
|| this.currentPonies.get(pal.getId()).getVersion() < pal.getVersion())
|
||||
{
|
||||
newListings.add(pal);
|
||||
}
|
||||
}
|
||||
|
||||
super.onClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareDialogBuilder(final AlertDialog.Builder builder)
|
||||
{
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
|
||||
final ListView listView = new ListView(getContext());
|
||||
listView.setAdapter(new PonyAnimationsAdapter(getContext(), 0, newListings));
|
||||
listView.setBackgroundColor(Color.WHITE);
|
||||
|
||||
builder.setView(listView);
|
||||
builder.setPositiveButton(null, null);
|
||||
}
|
||||
|
||||
private class PonyAnimationsAdapter extends ArrayAdapter<PonyAnimationListing> implements ListAdapter
|
||||
{
|
||||
|
||||
public PonyAnimationsAdapter(Context context, int textViewResourceId, List<PonyAnimationListing> objects)
|
||||
{
|
||||
super(context, textViewResourceId, objects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
final View view;
|
||||
if (convertView == null)
|
||||
{
|
||||
view = View.inflate(getContext(), R.layout.pony_listing, null);
|
||||
} else
|
||||
{
|
||||
view = convertView;
|
||||
}
|
||||
view.setId(position);
|
||||
((TextView)view.findViewById(R.id.title)).setText(this.getItem(position).getName());
|
||||
|
||||
if (!currentPonies.containsKey(newListings.get(position).getId()))
|
||||
{
|
||||
((TextView)view.findViewById(R.id.summary)).setText("New Pony!");
|
||||
}
|
||||
else
|
||||
{
|
||||
((TextView)view.findViewById(R.id.summary)).setText("Update Available!");
|
||||
}
|
||||
view.setOnClickListener(PonyDownloadListPreference.this);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
final int which = view.getId();
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
{
|
||||
private ProgressDialog dialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute()
|
||||
{
|
||||
dialog = ProgressDialog.show(getContext(), "", "Downloading...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... arg0)
|
||||
{
|
||||
try
|
||||
{
|
||||
PonyDownloader.fetchPony(getContext().getFilesDir(), getContext().getCacheDir(), newListings.get(which));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result)
|
||||
{
|
||||
dialog.dismiss();
|
||||
// Dismiss the list of choices
|
||||
getDialog().dismiss();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.derpfish.pinkielive.preference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.derpfish.pinkielive.download.PonyAnimationContainer;
|
||||
import com.derpfish.pinkielive.download.PonyDownloader;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class PonyListPreference extends ListPreference
|
||||
{
|
||||
public PonyListPreference(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PonyListPreference(Context context, AttributeSet attributeSet)
|
||||
{
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(final AlertDialog.Builder builder)
|
||||
{
|
||||
// Setup the dynamic pony list
|
||||
final List<PonyAnimationContainer> animations;
|
||||
try
|
||||
{
|
||||
animations = PonyDownloader.getPonyAnimations(
|
||||
getContext().getFilesDir(), getContext().getCacheDir(), false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
final String[] entries = new String[animations.size()+1];
|
||||
final String[] entryValues = new String[animations.size()+1];
|
||||
entries[0] = "Pinkie Pie";
|
||||
entryValues[0] = "pinkie";
|
||||
for (int i = 0; i < animations.size(); i++)
|
||||
{
|
||||
entries[i+1] = animations.get(i).getName();
|
||||
entryValues[i+1] = animations.get(i).getId();
|
||||
}
|
||||
this.setEntries(entries);
|
||||
this.setEntryValues(entryValues);
|
||||
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
}
|
||||
|
||||
}
|
742
src/com/derpfish/pinkielive/util/Base64.java
Normal file
742
src/com/derpfish/pinkielive/util/Base64.java
Normal file
@ -0,0 +1,742 @@
|
||||
// https://github.com/android/platform_frameworks_base/raw/master/core/java/android/util/Base64.java
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.derpfish.pinkielive.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Utilities for encoding and decoding the Base64 representation of
|
||||
* binary data. See RFCs <a
|
||||
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
|
||||
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
|
||||
*/
|
||||
public class Base64 {
|
||||
/**
|
||||
* Default values for encoder/decoder flags.
|
||||
*/
|
||||
public static final int DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Encoder flag bit to omit the padding '=' characters at the end
|
||||
* of the output (if any).
|
||||
*/
|
||||
public static final int NO_PADDING = 1;
|
||||
|
||||
/**
|
||||
* Encoder flag bit to omit all line terminators (i.e., the output
|
||||
* will be on one long line).
|
||||
*/
|
||||
public static final int NO_WRAP = 2;
|
||||
|
||||
/**
|
||||
* Encoder flag bit to indicate lines should be terminated with a
|
||||
* CRLF pair instead of just an LF. Has no effect if {@code
|
||||
* NO_WRAP} is specified as well.
|
||||
*/
|
||||
public static final int CRLF = 4;
|
||||
|
||||
/**
|
||||
* Encoder/decoder flag bit to indicate using the "URL and
|
||||
* filename safe" variant of Base64 (see RFC 3548 section 4) where
|
||||
* {@code -} and {@code _} are used in place of {@code +} and
|
||||
* {@code /}.
|
||||
*/
|
||||
public static final int URL_SAFE = 8;
|
||||
|
||||
/**
|
||||
* Flag to pass to {@link Base64OutputStream} to indicate that it
|
||||
* should not close the output stream it is wrapping when it
|
||||
* itself is closed.
|
||||
*/
|
||||
public static final int NO_CLOSE = 16;
|
||||
|
||||
// --------------------------------------------------------
|
||||
// shared code
|
||||
// --------------------------------------------------------
|
||||
|
||||
/* package */ static abstract class Coder {
|
||||
public byte[] output;
|
||||
public int op;
|
||||
|
||||
/**
|
||||
* Encode/decode another block of input data. this.output is
|
||||
* provided by the caller, and must be big enough to hold all
|
||||
* the coded data. On exit, this.opwill be set to the length
|
||||
* of the coded data.
|
||||
*
|
||||
* @param finish true if this is the final call to process for
|
||||
* this object. Will finalize the coder state and
|
||||
* include any final bytes in the output.
|
||||
*
|
||||
* @return true if the input so far is good; false if some
|
||||
* error has been detected in the input stream..
|
||||
*/
|
||||
public abstract boolean process(byte[] input, int offset, int len, boolean finish);
|
||||
|
||||
/**
|
||||
* @return the maximum number of bytes a call to process()
|
||||
* could produce for the given number of input bytes. This may
|
||||
* be an overestimate.
|
||||
*/
|
||||
public abstract int maxOutputSize(int len);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// decoding
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Decode the Base64-encoded data in input and return the data in
|
||||
* a new byte array.
|
||||
*
|
||||
* <p>The padding '=' characters at the end are considered optional, but
|
||||
* if any are present, there must be the correct number of them.
|
||||
*
|
||||
* @param str the input String to decode, which is converted to
|
||||
* bytes using the default charset
|
||||
* @param flags controls certain features of the decoded output.
|
||||
* Pass {@code DEFAULT} to decode standard Base64.
|
||||
*
|
||||
* @throws IllegalArgumentException if the input contains
|
||||
* incorrect padding
|
||||
*/
|
||||
public static byte[] decode(String str, int flags) {
|
||||
return decode(str.getBytes(), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the Base64-encoded data in input and return the data in
|
||||
* a new byte array.
|
||||
*
|
||||
* <p>The padding '=' characters at the end are considered optional, but
|
||||
* if any are present, there must be the correct number of them.
|
||||
*
|
||||
* @param input the input array to decode
|
||||
* @param flags controls certain features of the decoded output.
|
||||
* Pass {@code DEFAULT} to decode standard Base64.
|
||||
*
|
||||
* @throws IllegalArgumentException if the input contains
|
||||
* incorrect padding
|
||||
*/
|
||||
public static byte[] decode(byte[] input, int flags) {
|
||||
return decode(input, 0, input.length, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the Base64-encoded data in input and return the data in
|
||||
* a new byte array.
|
||||
*
|
||||
* <p>The padding '=' characters at the end are considered optional, but
|
||||
* if any are present, there must be the correct number of them.
|
||||
*
|
||||
* @param input the data to decode
|
||||
* @param offset the position within the input array at which to start
|
||||
* @param len the number of bytes of input to decode
|
||||
* @param flags controls certain features of the decoded output.
|
||||
* Pass {@code DEFAULT} to decode standard Base64.
|
||||
*
|
||||
* @throws IllegalArgumentException if the input contains
|
||||
* incorrect padding
|
||||
*/
|
||||
public static byte[] decode(byte[] input, int offset, int len, int flags) {
|
||||
// Allocate space for the most data the input could represent.
|
||||
// (It could contain less if it contains whitespace, etc.)
|
||||
Decoder decoder = new Decoder(flags, new byte[len*3/4]);
|
||||
|
||||
if (!decoder.process(input, offset, len, true)) {
|
||||
throw new IllegalArgumentException("bad base-64");
|
||||
}
|
||||
|
||||
// Maybe we got lucky and allocated exactly enough output space.
|
||||
if (decoder.op == decoder.output.length) {
|
||||
return decoder.output;
|
||||
}
|
||||
|
||||
// Need to shorten the array, so allocate a new one of the
|
||||
// right size and copy.
|
||||
byte[] temp = new byte[decoder.op];
|
||||
System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
|
||||
return temp;
|
||||
}
|
||||
|
||||
/* package */ static class Decoder extends Coder {
|
||||
/**
|
||||
* Lookup table for turning bytes into their position in the
|
||||
* Base64 alphabet.
|
||||
*/
|
||||
private static final int DECODE[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode lookup table for the "web safe" variant (RFC 3548
|
||||
* sec. 4) where - and _ replace + and /.
|
||||
*/
|
||||
private static final int DECODE_WEBSAFE[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
};
|
||||
|
||||
/** Non-data values in the DECODE arrays. */
|
||||
private static final int SKIP = -1;
|
||||
private static final int EQUALS = -2;
|
||||
|
||||
/**
|
||||
* States 0-3 are reading through the next input tuple.
|
||||
* State 4 is having read one '=' and expecting exactly
|
||||
* one more.
|
||||
* State 5 is expecting no more data or padding characters
|
||||
* in the input.
|
||||
* State 6 is the error state; an error has been detected
|
||||
* in the input and no future input can "fix" it.
|
||||
*/
|
||||
private int state; // state number (0 to 6)
|
||||
private int value;
|
||||
|
||||
final private int[] alphabet;
|
||||
|
||||
public Decoder(int flags, byte[] output) {
|
||||
this.output = output;
|
||||
|
||||
alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
|
||||
state = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an overestimate for the number of bytes {@code
|
||||
* len} bytes could decode to.
|
||||
*/
|
||||
public int maxOutputSize(int len) {
|
||||
return len * 3/4 + 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode another block of input data.
|
||||
*
|
||||
* @return true if the state machine is still healthy. false if
|
||||
* bad base-64 data has been detected in the input stream.
|
||||
*/
|
||||
public boolean process(byte[] input, int offset, int len, boolean finish) {
|
||||
if (this.state == 6) return false;
|
||||
|
||||
int p = offset;
|
||||
len += offset;
|
||||
|
||||
// Using local variables makes the decoder about 12%
|
||||
// faster than if we manipulate the member variables in
|
||||
// the loop. (Even alphabet makes a measurable
|
||||
// difference, which is somewhat surprising to me since
|
||||
// the member variable is final.)
|
||||
int state = this.state;
|
||||
int value = this.value;
|
||||
int op = 0;
|
||||
final byte[] output = this.output;
|
||||
final int[] alphabet = this.alphabet;
|
||||
|
||||
while (p < len) {
|
||||
// Try the fast path: we're starting a new tuple and the
|
||||
// next four bytes of the input stream are all data
|
||||
// bytes. This corresponds to going through states
|
||||
// 0-1-2-3-0. We expect to use this method for most of
|
||||
// the data.
|
||||
//
|
||||
// If any of the next four bytes of input are non-data
|
||||
// (whitespace, etc.), value will end up negative. (All
|
||||
// the non-data values in decode are small negative
|
||||
// numbers, so shifting any of them up and or'ing them
|
||||
// together will result in a value with its top bit set.)
|
||||
//
|
||||
// You can remove this whole block and the output should
|
||||
// be the same, just slower.
|
||||
if (state == 0) {
|
||||
while (p+4 <= len &&
|
||||
(value = ((alphabet[input[p] & 0xff] << 18) |
|
||||
(alphabet[input[p+1] & 0xff] << 12) |
|
||||
(alphabet[input[p+2] & 0xff] << 6) |
|
||||
(alphabet[input[p+3] & 0xff]))) >= 0) {
|
||||
output[op+2] = (byte) value;
|
||||
output[op+1] = (byte) (value >> 8);
|
||||
output[op] = (byte) (value >> 16);
|
||||
op += 3;
|
||||
p += 4;
|
||||
}
|
||||
if (p >= len) break;
|
||||
}
|
||||
|
||||
// The fast path isn't available -- either we've read a
|
||||
// partial tuple, or the next four input bytes aren't all
|
||||
// data, or whatever. Fall back to the slower state
|
||||
// machine implementation.
|
||||
|
||||
int d = alphabet[input[p++] & 0xff];
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (d >= 0) {
|
||||
value = d;
|
||||
++state;
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (d >= 0) {
|
||||
value = (value << 6) | d;
|
||||
++state;
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (d >= 0) {
|
||||
value = (value << 6) | d;
|
||||
++state;
|
||||
} else if (d == EQUALS) {
|
||||
// Emit the last (partial) output tuple;
|
||||
// expect exactly one more padding character.
|
||||
output[op++] = (byte) (value >> 4);
|
||||
state = 4;
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (d >= 0) {
|
||||
// Emit the output triple and return to state 0.
|
||||
value = (value << 6) | d;
|
||||
output[op+2] = (byte) value;
|
||||
output[op+1] = (byte) (value >> 8);
|
||||
output[op] = (byte) (value >> 16);
|
||||
op += 3;
|
||||
state = 0;
|
||||
} else if (d == EQUALS) {
|
||||
// Emit the last (partial) output tuple;
|
||||
// expect no further data or padding characters.
|
||||
output[op+1] = (byte) (value >> 2);
|
||||
output[op] = (byte) (value >> 10);
|
||||
op += 2;
|
||||
state = 5;
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (d == EQUALS) {
|
||||
++state;
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (d != SKIP) {
|
||||
this.state = 6;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finish) {
|
||||
// We're out of input, but a future call could provide
|
||||
// more.
|
||||
this.state = state;
|
||||
this.value = value;
|
||||
this.op = op;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Done reading input. Now figure out where we are left in
|
||||
// the state machine and finish up.
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
// Output length is a multiple of three. Fine.
|
||||
break;
|
||||
case 1:
|
||||
// Read one extra input byte, which isn't enough to
|
||||
// make another output byte. Illegal.
|
||||
this.state = 6;
|
||||
return false;
|
||||
case 2:
|
||||
// Read two extra input bytes, enough to emit 1 more
|
||||
// output byte. Fine.
|
||||
output[op++] = (byte) (value >> 4);
|
||||
break;
|
||||
case 3:
|
||||
// Read three extra input bytes, enough to emit 2 more
|
||||
// output bytes. Fine.
|
||||
output[op++] = (byte) (value >> 10);
|
||||
output[op++] = (byte) (value >> 2);
|
||||
break;
|
||||
case 4:
|
||||
// Read one padding '=' when we expected 2. Illegal.
|
||||
this.state = 6;
|
||||
return false;
|
||||
case 5:
|
||||
// Read all the padding '='s we expected and no more.
|
||||
// Fine.
|
||||
break;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
this.op = op;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// encoding
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* String with the result.
|
||||
*
|
||||
* @param input the data to encode
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* Passing {@code DEFAULT} results in output that
|
||||
* adheres to RFC 2045.
|
||||
*/
|
||||
public static String encodeToString(byte[] input, int flags) {
|
||||
try {
|
||||
return new String(encode(input, flags), "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// US-ASCII is guaranteed to be available.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* String with the result.
|
||||
*
|
||||
* @param input the data to encode
|
||||
* @param offset the position within the input array at which to
|
||||
* start
|
||||
* @param len the number of bytes of input to encode
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* Passing {@code DEFAULT} results in output that
|
||||
* adheres to RFC 2045.
|
||||
*/
|
||||
public static String encodeToString(byte[] input, int offset, int len, int flags) {
|
||||
try {
|
||||
return new String(encode(input, offset, len, flags), "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// US-ASCII is guaranteed to be available.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* byte[] with the result.
|
||||
*
|
||||
* @param input the data to encode
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* Passing {@code DEFAULT} results in output that
|
||||
* adheres to RFC 2045.
|
||||
*/
|
||||
public static byte[] encode(byte[] input, int flags) {
|
||||
return encode(input, 0, input.length, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* byte[] with the result.
|
||||
*
|
||||
* @param input the data to encode
|
||||
* @param offset the position within the input array at which to
|
||||
* start
|
||||
* @param len the number of bytes of input to encode
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* Passing {@code DEFAULT} results in output that
|
||||
* adheres to RFC 2045.
|
||||
*/
|
||||
public static byte[] encode(byte[] input, int offset, int len, int flags) {
|
||||
Encoder encoder = new Encoder(flags, null);
|
||||
|
||||
// Compute the exact length of the array we will produce.
|
||||
int output_len = len / 3 * 4;
|
||||
|
||||
// Account for the tail of the data and the padding bytes, if any.
|
||||
if (encoder.do_padding) {
|
||||
if (len % 3 > 0) {
|
||||
output_len += 4;
|
||||
}
|
||||
} else {
|
||||
switch (len % 3) {
|
||||
case 0: break;
|
||||
case 1: output_len += 2; break;
|
||||
case 2: output_len += 3; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Account for the newlines, if any.
|
||||
if (encoder.do_newline && len > 0) {
|
||||
output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
|
||||
(encoder.do_cr ? 2 : 1);
|
||||
}
|
||||
|
||||
encoder.output = new byte[output_len];
|
||||
encoder.process(input, offset, len, true);
|
||||
|
||||
assert encoder.op == output_len;
|
||||
|
||||
return encoder.output;
|
||||
}
|
||||
|
||||
/* package */ static class Encoder extends Coder {
|
||||
/**
|
||||
* Emit a new line every this many output tuples. Corresponds to
|
||||
* a 76-character line length (the maximum allowable according to
|
||||
* <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
|
||||
*/
|
||||
public static final int LINE_GROUPS = 19;
|
||||
|
||||
/**
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private static final byte ENCODE[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private static final byte ENCODE_WEBSAFE[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
|
||||
};
|
||||
|
||||
final private byte[] tail;
|
||||
/* package */ int tailLen;
|
||||
private int count;
|
||||
|
||||
final public boolean do_padding;
|
||||
final public boolean do_newline;
|
||||
final public boolean do_cr;
|
||||
final private byte[] alphabet;
|
||||
|
||||
public Encoder(int flags, byte[] output) {
|
||||
this.output = output;
|
||||
|
||||
do_padding = (flags & NO_PADDING) == 0;
|
||||
do_newline = (flags & NO_WRAP) == 0;
|
||||
do_cr = (flags & CRLF) != 0;
|
||||
alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
|
||||
|
||||
tail = new byte[2];
|
||||
tailLen = 0;
|
||||
|
||||
count = do_newline ? LINE_GROUPS : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an overestimate for the number of bytes {@code
|
||||
* len} bytes could encode to.
|
||||
*/
|
||||
public int maxOutputSize(int len) {
|
||||
return len * 8/5 + 10;
|
||||
}
|
||||
|
||||
public boolean process(byte[] input, int offset, int len, boolean finish) {
|
||||
// Using local variables makes the encoder about 9% faster.
|
||||
final byte[] alphabet = this.alphabet;
|
||||
final byte[] output = this.output;
|
||||
int op = 0;
|
||||
int count = this.count;
|
||||
|
||||
int p = offset;
|
||||
len += offset;
|
||||
int v = -1;
|
||||
|
||||
// First we need to concatenate the tail of the previous call
|
||||
// with any input bytes available now and see if we can empty
|
||||
// the tail.
|
||||
|
||||
switch (tailLen) {
|
||||
case 0:
|
||||
// There was no tail.
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (p+2 <= len) {
|
||||
// A 1-byte tail with at least 2 bytes of
|
||||
// input available now.
|
||||
v = ((tail[0] & 0xff) << 16) |
|
||||
((input[p++] & 0xff) << 8) |
|
||||
(input[p++] & 0xff);
|
||||
tailLen = 0;
|
||||
};
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (p+1 <= len) {
|
||||
// A 2-byte tail with at least 1 byte of input.
|
||||
v = ((tail[0] & 0xff) << 16) |
|
||||
((tail[1] & 0xff) << 8) |
|
||||
(input[p++] & 0xff);
|
||||
tailLen = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (v != -1) {
|
||||
output[op++] = alphabet[(v >> 18) & 0x3f];
|
||||
output[op++] = alphabet[(v >> 12) & 0x3f];
|
||||
output[op++] = alphabet[(v >> 6) & 0x3f];
|
||||
output[op++] = alphabet[v & 0x3f];
|
||||
if (--count == 0) {
|
||||
if (do_cr) output[op++] = '\r';
|
||||
output[op++] = '\n';
|
||||
count = LINE_GROUPS;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point either there is no tail, or there are fewer
|
||||
// than 3 bytes of input available.
|
||||
|
||||
// The main loop, turning 3 input bytes into 4 output bytes on
|
||||
// each iteration.
|
||||
while (p+3 <= len) {
|
||||
v = ((input[p] & 0xff) << 16) |
|
||||
((input[p+1] & 0xff) << 8) |
|
||||
(input[p+2] & 0xff);
|
||||
output[op] = alphabet[(v >> 18) & 0x3f];
|
||||
output[op+1] = alphabet[(v >> 12) & 0x3f];
|
||||
output[op+2] = alphabet[(v >> 6) & 0x3f];
|
||||
output[op+3] = alphabet[v & 0x3f];
|
||||
p += 3;
|
||||
op += 4;
|
||||
if (--count == 0) {
|
||||
if (do_cr) output[op++] = '\r';
|
||||
output[op++] = '\n';
|
||||
count = LINE_GROUPS;
|
||||
}
|
||||
}
|
||||
|
||||
if (finish) {
|
||||
// Finish up the tail of the input. Note that we need to
|
||||
// consume any bytes in tail before any bytes
|
||||
// remaining in input; there should be at most two bytes
|
||||
// total.
|
||||
|
||||
if (p-tailLen == len-1) {
|
||||
int t = 0;
|
||||
v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
|
||||
tailLen -= t;
|
||||
output[op++] = alphabet[(v >> 6) & 0x3f];
|
||||
output[op++] = alphabet[v & 0x3f];
|
||||
if (do_padding) {
|
||||
output[op++] = '=';
|
||||
output[op++] = '=';
|
||||
}
|
||||
if (do_newline) {
|
||||
if (do_cr) output[op++] = '\r';
|
||||
output[op++] = '\n';
|
||||
}
|
||||
} else if (p-tailLen == len-2) {
|
||||
int t = 0;
|
||||
v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
|
||||
(((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
|
||||
tailLen -= t;
|
||||
output[op++] = alphabet[(v >> 12) & 0x3f];
|
||||
output[op++] = alphabet[(v >> 6) & 0x3f];
|
||||
output[op++] = alphabet[v & 0x3f];
|
||||
if (do_padding) {
|
||||
output[op++] = '=';
|
||||
}
|
||||
if (do_newline) {
|
||||
if (do_cr) output[op++] = '\r';
|
||||
output[op++] = '\n';
|
||||
}
|
||||
} else if (do_newline && op > 0 && count != LINE_GROUPS) {
|
||||
if (do_cr) output[op++] = '\r';
|
||||
output[op++] = '\n';
|
||||
}
|
||||
|
||||
assert tailLen == 0;
|
||||
assert p == len;
|
||||
} else {
|
||||
// Save the leftovers in tail to be consumed on the next
|
||||
// call to encodeInternal.
|
||||
|
||||
if (p == len-1) {
|
||||
tail[tailLen++] = input[p];
|
||||
} else if (p == len-2) {
|
||||
tail[tailLen++] = input[p];
|
||||
tail[tailLen++] = input[p+1];
|
||||
}
|
||||
}
|
||||
|
||||
this.op = op;
|
||||
this.count = count;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Base64() { } // don't instantiate
|
||||
}
|
37
src/com/derpfish/pinkielive/util/IOUtils.java
Normal file
37
src/com/derpfish/pinkielive/util/IOUtils.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.derpfish.pinkielive.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class IOUtils
|
||||
{
|
||||
/**
|
||||
* Copies the bytes from istream to ostream
|
||||
*
|
||||
* @param istream
|
||||
* @param ostream
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void copyStream(final InputStream istream, final OutputStream ostream) throws IOException
|
||||
{
|
||||
final byte[] buffer = new byte[4096];
|
||||
int nread = 0;
|
||||
while ((nread = istream.read(buffer)) >= 0)
|
||||
{
|
||||
ostream.write(buffer, 0, nread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the bytes from istream to stream, closing both streams when the end of istream
|
||||
* is reached.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void copyStreamAndClose(final InputStream istream, final OutputStream ostream) throws IOException
|
||||
{
|
||||
copyStream(istream, ostream);
|
||||
istream.close();
|
||||
ostream.close();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user