NinePatch_Delegate.java revision 51a7e5447de94791c464cda5cc6ebbf616d73c80
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.layoutlib.bridge.impl.GcSnapshot;
23import com.android.ninepatch.NinePatchChunk;
24
25import android.graphics.drawable.NinePatchDrawable;
26
27import java.awt.Graphics2D;
28import java.awt.image.BufferedImage;
29import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
31import java.io.IOException;
32import java.io.ObjectInputStream;
33import java.io.ObjectOutputStream;
34import java.lang.ref.SoftReference;
35import java.util.HashMap;
36import java.util.Map;
37
38/**
39 * Delegate implementing the native methods of android.graphics.NinePatch
40 *
41 * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
42 * by calls to methods of the same name in this delegate class.
43 *
44 * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
45 * around to map int to instance of the delegate.
46 *
47 */
48public final class NinePatch_Delegate {
49
50    /**
51     * Cache map for {@link NinePatchChunk}.
52     * When the chunks are created they are serialized into a byte[], and both are put
53     * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
54     * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
55     * provide this for drawing.
56     * Using the cache map allows us to not have to deserialize the byte[] back into a
57     * {@link NinePatchChunk} every time a rendering is done.
58     */
59    private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
60        new HashMap<byte[], SoftReference<NinePatchChunk>>();
61
62    // ---- Public Helper methods ----
63
64    /**
65     * Serializes the given chunk.
66     *
67     * @return the serialized data for the chunk.
68     */
69    public static byte[] serialize(NinePatchChunk chunk) {
70        // serialize the chunk to get a byte[]
71        ByteArrayOutputStream baos = new ByteArrayOutputStream();
72        ObjectOutputStream oos = null;
73        try {
74            oos = new ObjectOutputStream(baos);
75            oos.writeObject(chunk);
76        } catch (IOException e) {
77            Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/);
78            return null;
79        } finally {
80            if (oos != null) {
81                try {
82                    oos.close();
83                } catch (IOException e) {
84                }
85            }
86        }
87
88        // get the array and add it to the cache
89        byte[] array = baos.toByteArray();
90        sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
91        return array;
92    }
93
94    // ---- native methods ----
95
96    /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
97        NinePatchChunk chunkObject = getChunk(chunk);
98        if (chunkObject != null) {
99            return true;
100        }
101
102        return false;
103    }
104
105    /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) {
106        // the default JNI implementation only checks that the byte[] has the same
107        // size as the C struct it represent. Since we cannot do the same check (serialization
108        // will return different size depending on content), we do nothing.
109    }
110
111    /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
112            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
113        draw(canvas_instance,
114                (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
115                bitmap_instance, c, paint_instance_or_null,
116                destDensity, srcDensity);
117    }
118
119    /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
120            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
121        draw(canvas_instance,
122                loc.left, loc.top, loc.width(), loc.height(),
123                bitmap_instance, c, paint_instance_or_null,
124                destDensity, srcDensity);
125    }
126
127   private static void draw(int canvas_instance,
128           final int left, final int top, final int right, final int bottom,
129           int bitmap_instance, byte[] c, int paint_instance_or_null,
130           final int destDensity, final int srcDensity) {
131       // get the delegate from the native int.
132       final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
133       if (bitmap_delegate == null) {
134           return;
135       }
136
137       if (c == null) {
138           // not a 9-patch?
139           BufferedImage image = bitmap_delegate.getImage();
140           Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance,
141                   new Rect(0, 0, image.getWidth(), image.getHeight()),
142                   new Rect(left, top, right, bottom),
143                   paint_instance_or_null, destDensity, srcDensity);
144           return;
145       }
146
147       final NinePatchChunk chunkObject = getChunk(c);
148       assert chunkObject != null;
149       if (chunkObject == null) {
150           return;
151       }
152
153       Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
154       if (canvas_delegate == null) {
155           return;
156       }
157
158       // this one can be null
159       Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
160
161       canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
162               public void draw(Graphics2D graphics, Paint_Delegate paint) {
163                   chunkObject.draw(bitmap_delegate.getImage(), graphics,
164                           left, top, right - left, bottom - top, destDensity, srcDensity);
165               }
166           }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
167
168    }
169
170    /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {
171        return 0;
172    }
173
174    // ---- Private Helper methods ----
175
176    /**
177     * Returns a {@link NinePatchChunk} object for the given serialized representation.
178     *
179     * If the chunk is present in the cache then the object from the cache is returned, otherwise
180     * the array is deserialized into a {@link NinePatchChunk} object.
181     *
182     * @param array the serialized representation of the chunk.
183     * @return the NinePatchChunk or null if deserialization failed.
184     */
185    private static NinePatchChunk getChunk(byte[] array) {
186        SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
187        NinePatchChunk chunk = chunkRef.get();
188        if (chunk == null) {
189            ByteArrayInputStream bais = new ByteArrayInputStream(array);
190            ObjectInputStream ois = null;
191            try {
192                ois = new ObjectInputStream(bais);
193                chunk = (NinePatchChunk) ois.readObject();
194
195                // put back the chunk in the cache
196                if (chunk != null) {
197                    sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
198                }
199            } catch (IOException e) {
200                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
201                        "Failed to deserialize NinePatchChunk content.", e, null /*data*/);
202                return null;
203            } catch (ClassNotFoundException e) {
204                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
205                        "Failed to deserialize NinePatchChunk class.", e, null /*data*/);
206                return null;
207            } finally {
208                if (ois != null) {
209                    try {
210                        ois.close();
211                    } catch (IOException e) {
212                    }
213                }
214            }
215        }
216
217        return chunk;
218    }
219}
220