diff options
Diffstat (limited to 'src/org/cyanogenmod/theme/util/BootAnimationHelper.java')
-rw-r--r-- | src/org/cyanogenmod/theme/util/BootAnimationHelper.java | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/theme/util/BootAnimationHelper.java b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java new file mode 100644 index 0000000..1bd4ee7 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.ThemeConfig; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class BootAnimationHelper { + private static final String TAG = BootAnimationHelper.class.getSimpleName(); + private static final int MAX_REPEAT_COUNT = 3; + + public static final String THEME_INTERNAL_BOOT_ANI_PATH = + "assets/bootanimation/bootanimation.zip"; + public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip"; + public static final String CACHED_SUFFIX = "_bootanimation.zip"; + + public static final int NUM_FIRST_LINE_PARAMETERS = 3; + public static final int NUM_PART_LINE_PARAMETERS = 4; + + public static class AnimationPart { + /** + * Number of times to play this part + */ + public int playCount; + /** + * If non-zero, pause for the given # of seconds before moving on to next part. + */ + public int pause; + /** + * The name of this part + */ + public String partName; + /** + * Time each frame is displayed + */ + public int frameRateMillis; + /** + * List of file names for the given frames in this part + */ + public List<String> frames; + /** + * width of the animation + */ + public int width; + /** + * height of the animation + */ + public int height; + + public AnimationPart(int playCount, int pause, String partName, int frameRateMillis, + int width, int height) { + this.playCount = playCount == 0 ? MAX_REPEAT_COUNT : playCount; + this.pause = pause; + this.partName = partName; + this.frameRateMillis = frameRateMillis; + this.width = width; + this.height = height; + frames = new ArrayList<String>(); + } + + public void addFrame(String frame) { + frames.add(frame); + } + } + + /** + * Gather up all the details for the given boot animation + * @param zip The bootanimation.zip + * @return A list of AnimationPart if successful, null if not. + * @throws IOException + */ + public static List<AnimationPart> parseAnimation(ZipFile zip) + throws IOException, BootAnimationException { + if (zip == null) { + // To make tracking down boot animation problems we'll throw a BootAnimationException + // instead of an IllegalArgumentException. + throw new BootAnimationException("Boot animation ZipFile cannot be null"); + } + List<AnimationPart> animationParts = null; + + ZipEntry ze = zip.getEntry("desc.txt"); + if (ze != null) { + animationParts = parseDescription(zip.getInputStream(ze)); + } else { + throw new BootAnimationException("Missing desc.txt in root of bootanimation.zip"); + } + + if (animationParts == null) { + // We really should not end up here but in case we do here's an exception for ya! + throw new BootAnimationException("Unable to load boot animation."); + } + + Iterator<AnimationPart> iterator = animationParts.iterator(); + while(iterator.hasNext()) { + AnimationPart a = iterator.next(); + for (Enumeration<? extends ZipEntry> e = zip.entries();e.hasMoreElements();) { + ze = e.nextElement(); + if (!ze.isDirectory() && ze.getName().contains(a.partName)) { + a.addFrame(ze.getName()); + } + } + if (a.frames.size() > 0) { + Collections.sort(a.frames); + } else { + // This boot animation may be salvageable if there are still some other parts + // that are good. We'll remove this part and if there are no parts left by + // the time we have iterated over all the parts then we can throw an exception. + Log.w(TAG, String.format("No frames in part %s, removing from animation", + a.partName)); + iterator.remove(); + } + } + if (animationParts.size() == 0) { + throw new BootAnimationException("Boot animation must have at least one part."); + } + + return animationParts; + } + + /** + * Parses the desc.txt of the boot animation + * @param in InputStream to the desc.txt + * @return A list of the parts as given in desc.txt + * @throws IOException + */ + private static List<AnimationPart> parseDescription(InputStream in) + throws IOException, BootAnimationException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // read in suggested width, height, and frame rate from first line + String line = reader.readLine(); + String[] details = line.split(" "); + if (details.length != NUM_FIRST_LINE_PARAMETERS) { + throw new BootAnimationException(String.format( + "Invalid # of parameters on first line of desc.txt; exptected %d, read %d " + + "(\"%s\")", + NUM_FIRST_LINE_PARAMETERS, details.length, line)); + } + + // The items should be in the following order: width, height, frame rate + final int width = Integer.parseInt(details[0]); + final int height = Integer.parseInt(details[1]); + final int frameRateMillis = 1000 / Integer.parseInt(details[2]); + + List<AnimationPart> animationParts = new ArrayList<AnimationPart>(); + while ((line = reader.readLine()) != null) { + // trim off any leading and trailing spaces + line = line.trim(); + // if the line is empty continue on to the next + if (TextUtils.isEmpty(line)) continue; + + String[] info = line. split(" "); + if (info.length != NUM_PART_LINE_PARAMETERS) { + Log.w(TAG, String.format( + "Invalid # of part parameters; exptected %d, read %d (\"%s\")", + NUM_PART_LINE_PARAMETERS, info.length, line)); + // let's continue in case there are parts that are valid + continue; + } + if (!info[0].equals("p") && !info[0].equals("c")) { + Log.w(TAG, String.format( + "Unknown part type; expected 'p' or 'c', read %s (\"%s\")", info[0], line)); + + // let's continue in case there are parts that are valid + continue; + } + int playCount = Integer.parseInt(info[1]); + int pause = Integer.parseInt(info[2]); + String name = info[3]; + AnimationPart ap = new AnimationPart(playCount, pause, name, frameRateMillis, + width, height); + animationParts.add(ap); + } + + return animationParts; + } + + public static String getPreviewFrameEntryName(InputStream is) throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + String previewName = null; + while ((ze = zis.getNextEntry()) != null) { + final String entryName = ze.getName(); + if (entryName.contains("/") + && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) { + previewName = entryName; + } + } + + return previewName; + } + + public static Bitmap loadPreviewFrame(Context context, InputStream is, String previewName) + throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = am.isLowRamDevice() ? 4 : 2; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + Bitmap preview = null; + while ((ze = zis.getNextEntry()) != null && preview == null) { + final String entryName = ze.getName(); + if (entryName.equals(previewName)) { + preview = BitmapFactory.decodeStream(zis, null, opts); + } + } + zis.close(); + + return preview; + } + + public static void clearBootAnimationCache(Context context) { + File cache = context.getCacheDir(); + if (cache.exists()) { + for(File f : cache.listFiles()) { + // volley stores stuff in cache so don't delete the volley directory + if(!f.isDirectory() && f.getName().endsWith(CACHED_SUFFIX)) f.delete(); + } + } + } + + public static class LoadBootAnimationImage extends AsyncTask<Object, Void, Bitmap> { + private ImageView imv; + private String path; + private Context context; + + public LoadBootAnimationImage(ImageView imv, Context context, String path) { + this.imv = imv; + this.context = context; + this.path = path; + } + + @Override + protected Bitmap doInBackground(Object... params) { + Bitmap bitmap = null; + String previewName = null; + // this is ugly, ugly, ugly. Did I mention this is ugly? + try { + if (ThemeConfig.SYSTEM_DEFAULT.equals(path)) { + previewName = getPreviewFrameEntryName( + new FileInputStream(SYSTEM_BOOT_ANI_PATH)); + bitmap = loadPreviewFrame( + context, new FileInputStream(SYSTEM_BOOT_ANI_PATH), previewName); + } else { + final Context themeCtx = context.createPackageContext(path, 0); + previewName = getPreviewFrameEntryName( + themeCtx.getAssets().open("bootanimation/bootanimation.zip")); + bitmap = loadPreviewFrame(context, + themeCtx.getAssets().open("bootanimation/bootanimation.zip"), + previewName); + } + } catch (Exception e) { + // don't care since a null bitmap will be returned + e.printStackTrace(); + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null && imv != null) { + imv.setVisibility(View.VISIBLE); + imv.setImageBitmap(result); + } + } + } + + public static class BootAnimationException extends Exception { + public BootAnimationException(String detailMessage) { + super(detailMessage); + } + } +} |