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 
17 package com.android.layoutlib.bridge.impl;
18 
19 import com.android.ide.common.rendering.api.HardwareConfig;
20 import com.android.ide.common.rendering.api.LayoutLog;
21 import com.android.ide.common.rendering.api.RenderParams;
22 import com.android.ide.common.rendering.api.RenderResources;
23 import com.android.ide.common.rendering.api.Result;
24 import com.android.layoutlib.bridge.Bridge;
25 import com.android.layoutlib.bridge.android.BridgeContext;
26 import com.android.resources.Density;
27 import com.android.resources.ScreenOrientation;
28 import com.android.resources.ScreenRound;
29 import com.android.resources.ScreenSize;
30 import com.android.tools.layoutlib.annotations.VisibleForTesting;
31 
32 import android.animation.PropertyValuesHolder_Accessor;
33 import android.content.res.Configuration;
34 import android.os.HandlerThread_Delegate;
35 import android.util.DisplayMetrics;
36 import android.view.IWindowManager;
37 import android.view.IWindowManagerImpl;
38 import android.view.Surface;
39 import android.view.ViewConfiguration_Accessor;
40 import android.view.WindowManagerGlobal_Delegate;
41 import android.view.inputmethod.InputMethodManager_Accessor;
42 
43 import java.util.Locale;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.locks.ReentrantLock;
46 
47 import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
48 import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
49 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
50 
51 /**
52  * Base class for rendering action.
53  *
54  * It provides life-cycle methods to init and stop the rendering.
55  * The most important methods are:
56  * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()}
57  * after the rendering.
58  *
59  *
60  * @param <T> the {@link RenderParams} implementation
61  *
62  */
63 public abstract class RenderAction<T extends RenderParams> {
64 
65     /**
66      * The current context being rendered. This is set through {@link #acquire(long)} and
67      * {@link #init(long)}, and unset in {@link #release()}.
68      */
69     @VisibleForTesting
70     static BridgeContext sCurrentContext = null;
71 
72     private final T mParams;
73 
74     private BridgeContext mContext;
75 
76     /**
77      * Creates a renderAction.
78      * <p>
79      * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a
80      * call to {@link RenderAction#acquire(long)}
81      *
82      * @param params the RenderParams. This must be a copy that the action can keep
83      *
84      */
RenderAction(T params)85     protected RenderAction(T params) {
86         mParams = params;
87     }
88 
89     /**
90      * Initializes and acquires the scene, creating various Android objects such as context,
91      * inflater, and parser.
92      *
93      * @param timeout the time to wait if another rendering is happening.
94      *
95      * @return whether the scene was prepared
96      *
97      * @see #acquire(long)
98      * @see #release()
99      */
init(long timeout)100     public Result init(long timeout) {
101         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
102         // the result.
103         Result result = acquireLock(timeout);
104         if (result != null) {
105             return result;
106         }
107 
108         HardwareConfig hardwareConfig = mParams.getHardwareConfig();
109 
110         // setup the display Metrics.
111         DisplayMetrics metrics = new DisplayMetrics();
112         metrics.densityDpi = metrics.noncompatDensityDpi =
113                 hardwareConfig.getDensity().getDpiValue();
114 
115         metrics.density = metrics.noncompatDensity =
116                 metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
117 
118         metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
119 
120         metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
121         metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
122         metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
123         metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
124 
125         RenderResources resources = mParams.getResources();
126 
127         // build the context
128         mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
129                 mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams),
130                 mParams.getTargetSdkVersion(), mParams.isRtlSupported());
131 
132         setUp();
133 
134         return SUCCESS.createResult();
135     }
136 
137     /**
138      * Prepares the scene for action.
139      * <p>
140      * This call is blocking if another rendering/inflating is currently happening, and will return
141      * whether the preparation worked.
142      *
143      * The preparation can fail if another rendering took too long and the timeout was elapsed.
144      *
145      * More than one call to this from the same thread will have no effect and will return
146      * {@link Result.Status#SUCCESS}.
147      *
148      * After scene actions have taken place, only one call to {@link #release()} must be
149      * done.
150      *
151      * @param timeout the time to wait if another rendering is happening.
152      *
153      * @return whether the scene was prepared
154      *
155      * @see #release()
156      *
157      * @throws IllegalStateException if {@link #init(long)} was never called.
158      */
acquire(long timeout)159     public Result acquire(long timeout) {
160         if (mContext == null) {
161             throw new IllegalStateException("After scene creation, #init() must be called");
162         }
163 
164         // acquire the lock. if the result is null, lock was just acquired, otherwise, return
165         // the result.
166         Result result = acquireLock(timeout);
167         if (result != null) {
168             return result;
169         }
170 
171         setUp();
172 
173         return SUCCESS.createResult();
174     }
175 
176     /**
177      * Acquire the lock so that the scene can be acted upon.
178      * <p>
179      * This returns null if the lock was just acquired, otherwise it returns
180      * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another
181      * instance (see {@link Result#getStatus()}) if an error occurred.
182      *
183      * @param timeout the time to wait if another rendering is happening.
184      * @return null if the lock was just acquire or another result depending on the state.
185      *
186      * @throws IllegalStateException if the current context is different than the one owned by
187      *      the scene.
188      */
acquireLock(long timeout)189     private Result acquireLock(long timeout) {
190         ReentrantLock lock = Bridge.getLock();
191         if (!lock.isHeldByCurrentThread()) {
192             try {
193                 boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
194 
195                 if (!acquired) {
196                     return ERROR_TIMEOUT.createResult();
197                 }
198             } catch (InterruptedException e) {
199                 return ERROR_LOCK_INTERRUPTED.createResult();
200             }
201         } else {
202             // This thread holds the lock already. Checks that this wasn't for a different context.
203             // If this is called by init, mContext will be null and so should sCurrentContext
204             // anyway
205             if (mContext != sCurrentContext) {
206                 throw new IllegalStateException("Acquiring different scenes from same thread without releases");
207             }
208             return SUCCESS.createResult();
209         }
210 
211         return null;
212     }
213 
214     /**
215      * Cleans up the scene after an action.
216      */
release()217     public void release() {
218         ReentrantLock lock = Bridge.getLock();
219 
220         // with the use of finally blocks, it is possible to find ourself calling this
221         // without a successful call to prepareScene. This test makes sure that unlock() will
222         // not throw IllegalMonitorStateException.
223         if (lock.isHeldByCurrentThread()) {
224             tearDown();
225             lock.unlock();
226         }
227     }
228 
229     /**
230      * Sets up the session for rendering.
231      * <p/>
232      * The counterpart is {@link #tearDown()}.
233      */
setUp()234     private void setUp() {
235         // setup the ParserFactory
236         ParserFactory.setParserFactory(mParams.getLayoutlibCallback());
237 
238         // make sure the Resources object references the context (and other objects) for this
239         // scene
240         mContext.initResources();
241         sCurrentContext = mContext;
242 
243         // Set-up WindowManager
244         // FIXME: find those out, and possibly add them to the render params
245         boolean hasNavigationBar = true;
246         //noinspection ConstantConditions
247         IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
248                 getContext().getMetrics(), Surface.ROTATION_0, hasNavigationBar);
249         WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
250 
251         LayoutLog currentLog = mParams.getLog();
252         Bridge.setLog(currentLog);
253         mContext.getRenderResources().setLogger(currentLog);
254     }
255 
256     /**
257      * Tear down the session after rendering.
258      * <p/>
259      * The counterpart is {@link #setUp()}.
260      */
tearDown()261     private void tearDown() {
262         // The context may be null, if there was an error during init().
263         if (mContext != null) {
264             // Make sure to remove static references, otherwise we could not unload the lib
265             mContext.disposeResources();
266         }
267 
268         if (sCurrentContext != null) {
269             // quit HandlerThread created during this session.
270             HandlerThread_Delegate.cleanUp(sCurrentContext);
271         }
272 
273         // clear the stored ViewConfiguration since the map is per density and not per context.
274         ViewConfiguration_Accessor.clearConfigurations();
275 
276         // remove the InputMethodManager
277         InputMethodManager_Accessor.tearDownEditMode();
278 
279         sCurrentContext = null;
280 
281         Bridge.setLog(null);
282         if (mContext != null) {
283             mContext.getRenderResources().setLogger(null);
284         }
285         ParserFactory.setParserFactory(null);
286 
287         PropertyValuesHolder_Accessor.clearClassCaches();
288     }
289 
getCurrentContext()290     public static BridgeContext getCurrentContext() {
291         return sCurrentContext;
292     }
293 
getParams()294     protected T getParams() {
295         return mParams;
296     }
297 
getContext()298     protected BridgeContext getContext() {
299         return mContext;
300     }
301 
302     /**
303      * Returns the log associated with the session.
304      * @return the log or null if there are none.
305      */
getLog()306     public LayoutLog getLog() {
307         if (mParams != null) {
308             return mParams.getLog();
309         }
310 
311         return null;
312     }
313 
314     /**
315      * Checks that the lock is owned by the current thread and that the current context is the one
316      * from this scene.
317      *
318      * @throws IllegalStateException if the current context is different than the one owned by
319      *      the scene, or if {@link #acquire(long)} was not called.
320      */
checkLock()321     protected void checkLock() {
322         ReentrantLock lock = Bridge.getLock();
323         if (!lock.isHeldByCurrentThread()) {
324             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
325         }
326         if (sCurrentContext != mContext) {
327             throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
328         }
329     }
330 
331     // VisibleForTesting
getConfiguration(RenderParams params)332     public static Configuration getConfiguration(RenderParams params) {
333         Configuration config = new Configuration();
334 
335         HardwareConfig hardwareConfig = params.getHardwareConfig();
336 
337         ScreenSize screenSize = hardwareConfig.getScreenSize();
338         if (screenSize != null) {
339             switch (screenSize) {
340                 case SMALL:
341                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
342                     break;
343                 case NORMAL:
344                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
345                     break;
346                 case LARGE:
347                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
348                     break;
349                 case XLARGE:
350                     config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
351                     break;
352             }
353         }
354 
355         Density density = hardwareConfig.getDensity();
356         if (density == null) {
357             density = Density.MEDIUM;
358         }
359 
360         config.screenWidthDp = hardwareConfig.getScreenWidth() * 160 / density.getDpiValue();
361         config.screenHeightDp = hardwareConfig.getScreenHeight() * 160 / density.getDpiValue();
362         if (config.screenHeightDp < config.screenWidthDp) {
363             //noinspection SuspiciousNameCombination
364             config.smallestScreenWidthDp = config.screenHeightDp;
365         } else {
366             config.smallestScreenWidthDp = config.screenWidthDp;
367         }
368         config.densityDpi = density.getDpiValue();
369 
370         // never run in compat mode:
371         config.compatScreenWidthDp = config.screenWidthDp;
372         config.compatScreenHeightDp = config.screenHeightDp;
373 
374         ScreenOrientation orientation = hardwareConfig.getOrientation();
375         if (orientation != null) {
376             switch (orientation) {
377             case PORTRAIT:
378                 config.orientation = Configuration.ORIENTATION_PORTRAIT;
379                 break;
380             case LANDSCAPE:
381                 config.orientation = Configuration.ORIENTATION_LANDSCAPE;
382                 break;
383             case SQUARE:
384                 //noinspection deprecation
385                 config.orientation = Configuration.ORIENTATION_SQUARE;
386                 break;
387             }
388         } else {
389             config.orientation = Configuration.ORIENTATION_UNDEFINED;
390         }
391 
392         ScreenRound roundness = hardwareConfig.getScreenRoundness();
393         if (roundness != null) {
394             switch (roundness) {
395                 case ROUND:
396                     config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
397                     break;
398                 case NOTROUND:
399                     config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_NO;
400             }
401         } else {
402             config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
403         }
404         String locale = params.getLocale();
405         if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale);
406 
407         // TODO: fill in more config info.
408 
409         return config;
410     }
411 }
412