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