2D Graphics Android

/*
 * Copyright (C) 2007 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 app.test;
import java.io.ByteArrayOutputStream;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
/**
 * PurgeableBitmap demonstrates the effects of setting Bitmaps as being
 * purgeable.
 * 
 * In the NonPurgeable case, an encoded bitstream is decoded to a different
 * Bitmap over and over again up to 200 times until out-of-memory occurs. In
 * contrast, the Purgeable case shows that the system can complete decoding the
 * encoded bitstream 200 times without hitting the out-of-memory case.
 */
public class Test extends GraphicsActivity {
  private PurgeableBitmapView mView;
  private final RefreshHandler mRedrawHandler = new RefreshHandler();
  class RefreshHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
      int index = mView.update(this);
      if (index > 0) {
        showAlertDialog(getDialogMessage(true, index));
      } else if (index < 0) {
        mView.invalidate();
        showAlertDialog(getDialogMessage(false, -index));
      } else {
        mView.invalidate();
      }
    }
    public void sleep(long delayMillis) {
      this.removeMessages(0);
      sendMessageDelayed(obtainMessage(0), delayMillis);
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mView = new PurgeableBitmapView(this, detectIfPurgeableRequest());
    mRedrawHandler.sleep(0);
    setContentView(mView);
  }
  private boolean detectIfPurgeableRequest() {
    PackageManager pm = getPackageManager();
    CharSequence labelSeq = null;
    try {
      ActivityInfo info = pm.getActivityInfo(this.getComponentName(),
          PackageManager.GET_META_DATA);
      labelSeq = info.loadLabel(pm);
    } catch (NameNotFoundException e) {
      e.printStackTrace();
      return false;
    }
    String[] components = labelSeq.toString().split("/");
    if (components[components.length - 1].equals("Purgeable")) {
      return true;
    } else {
      return false;
    }
  }
  private String getDialogMessage(boolean isOutOfMemory, int index) {
    StringBuilder sb = new StringBuilder();
    if (isOutOfMemory) {
      sb.append("Out of memery occurs when the ");
      sb.append(index);
      sb.append("th Bitmap is decoded.");
    } else {
      sb.append("Complete decoding ").append(index)
          .append(" bitmaps without running out of memory.");
    }
    return sb.toString();
  }
  private void showAlertDialog(String message) {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage(message)
        .setCancelable(false)
        .setPositiveButton("Yes",
            new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int id) {
              }
            });
    AlertDialog alert = builder.create();
    alert.show();
  }
}
/**
 * PurgeableBitmapView works with PurgeableBitmap to demonstrate the effects of
 * setting Bitmaps as being purgeable.
 * 
 * PurgeableBitmapView decodes an encoded bitstream to a Bitmap each time
 * update() is invoked(), and its onDraw() draws the Bitmap and a number to
 * screen. The number is used to indicate the number of Bitmaps that has been
 * decoded.
 */
class PurgeableBitmapView extends View {
  private final byte[] bitstream;
  private Bitmap mBitmap;
  private final int mArraySize = 200;
  private final Bitmap[] mBitmapArray = new Bitmap[mArraySize];
  private final Options mOptions = new Options();
  private static final int WIDTH = 150;
  private static final int HEIGHT = 450;
  private static final int STRIDE = 320; // must be >= WIDTH
  private int mDecodingCount = 0;
  private final Paint mPaint = new Paint();
  private final int textSize = 32;
  private static int delay = 100;
  public PurgeableBitmapView(Context context, boolean isPurgeable) {
    super(context);
    setFocusable(true);
    mOptions.inPurgeable = isPurgeable;
    int[] colors = createColors();
    Bitmap src = Bitmap.createBitmap(colors, 0, STRIDE, WIDTH, HEIGHT,
        Bitmap.Config.ARGB_8888);
    bitstream = generateBitstream(src, Bitmap.CompressFormat.JPEG, 80);
    mPaint.setTextSize(textSize);
    mPaint.setColor(Color.GRAY);
  }
  private int[] createColors() {
    int[] colors = new int[STRIDE * HEIGHT];
    for (int y = 0; y < HEIGHT; y++) {
      for (int x = 0; x < WIDTH; x++) {
        int r = x * 255 / (WIDTH - 1);
        int g = y * 255 / (HEIGHT - 1);
        int b = 255 - Math.min(r, g);
        int a = Math.max(r, g);
        colors[y * STRIDE + x] = (a << 24) | (r << 16) | (g << 8) | b;
      }
    }
    return colors;
  }
  public int update(Test.RefreshHandler handler) {
    try {
      mBitmapArray[mDecodingCount] = BitmapFactory.decodeByteArray(
          bitstream, 0, bitstream.length, mOptions);
      mBitmap = mBitmapArray[mDecodingCount];
      mDecodingCount++;
      if (mDecodingCount < mArraySize) {
        handler.sleep(delay);
        return 0;
      } else {
        return -mDecodingCount;
      }
    } catch (OutOfMemoryError error) {
      for (int i = 0; i < mDecodingCount; i++) {
        mBitmapArray[i].recycle();
      }
      return mDecodingCount + 1;
    }
  }
  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawColor(Color.WHITE);
    canvas.drawBitmap(mBitmap, 0, 0, null);
    canvas.drawText(String.valueOf(mDecodingCount), WIDTH / 2 - 20,
        HEIGHT / 2, mPaint);
  }
  private byte[] generateBitstream(Bitmap src, Bitmap.CompressFormat format,
      int quality) {
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    src.compress(format, quality, os);
    return os.toByteArray();
  }
}
class GraphicsActivity extends Activity {
  // set to true to test Picture
  private static final boolean TEST_PICTURE = false;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }
  @Override
  public void setContentView(View view) {
    if (TEST_PICTURE) {
      ViewGroup vg = new PictureLayout(this);
      vg.addView(view);
      view = vg;
    }
    super.setContentView(view);
  }
}
class PictureLayout extends ViewGroup {
  private final Picture mPicture = new Picture();
  public PictureLayout(Context context) {
    super(context);
  }
  public PictureLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
  @Override
  public void addView(View child) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }
    super.addView(child);
  }
  @Override
  public void addView(View child, int index) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }
    super.addView(child, index);
  }
  @Override
  public void addView(View child, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }
    super.addView(child, params);
  }
  @Override
  public void addView(View child, int index, LayoutParams params) {
    if (getChildCount() > 1) {
      throw new IllegalStateException(
          "PictureLayout can host only one direct child");
    }
    super.addView(child, index, params);
  }
  @Override
  protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT,
        LayoutParams.MATCH_PARENT);
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int count = getChildCount();
    int maxHeight = 0;
    int maxWidth = 0;
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
      }
    }
    maxWidth += getPaddingLeft() + getPaddingRight();
    maxHeight += getPaddingTop() + getPaddingBottom();
    Drawable drawable = getBackground();
    if (drawable != null) {
      maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
      maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }
    setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
        resolveSize(maxHeight, heightMeasureSpec));
  }
  private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
      float sy) {
    canvas.save();
    canvas.translate(x, y);
    canvas.clipRect(0, 0, w, h);
    canvas.scale(0.5f, 0.5f);
    canvas.scale(sx, sy, w, h);
    canvas.drawPicture(mPicture);
    canvas.restore();
  }
  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
    mPicture.endRecording();
    int x = getWidth() / 2;
    int y = getHeight() / 2;
    if (false) {
      canvas.drawPicture(mPicture);
    } else {
      drawPict(canvas, 0, 0, x, y, 1, 1);
      drawPict(canvas, x, 0, x, y, -1, 1);
      drawPict(canvas, 0, y, x, y, 1, -1);
      drawPict(canvas, x, y, x, y, -1, -1);
    }
  }
  @Override
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    location[0] = getLeft();
    location[1] = getTop();
    dirty.set(0, 0, getWidth(), getHeight());
    return getParent();
  }
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = super.getChildCount();
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        child.layout(childLeft, childTop,
            childLeft + child.getMeasuredWidth(),
            childTop + child.getMeasuredHeight());
      }
    }
  }
}