RenderSessionImplpublic class RenderSessionImpl extends RenderAction Class implementing the render session.
A session is a stateful representation of a layout file. It is initialized with data coming
through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
be done on the layout. |
Fields Summary |
---|
private static final int | DEFAULT_TITLE_BAR_HEIGHT | private static final int | DEFAULT_STATUS_BAR_HEIGHT | private com.android.ide.common.rendering.api.RenderSession | mScene | private com.android.layoutlib.bridge.android.BridgeXmlBlockParser | mBlockParser | private android.view.BridgeInflater | mInflater | private com.android.ide.common.rendering.api.ResourceValue | mWindowBackground | private android.view.ViewGroup | mViewRoot | private android.widget.FrameLayout | mContentRoot | private android.graphics.Canvas | mCanvas | private int | mMeasuredScreenWidth | private int | mMeasuredScreenHeight | private boolean | mIsAlphaChannelImage | private boolean | mWindowIsFloating | private Boolean | mIsThemeAppCompat | private int | mStatusBarSize | private int | mNavigationBarSize | private int | mNavigationBarOrientation | private int | mTitleBarSize | private int | mActionBarSize | private BufferedImage | mImage | private List | mViewInfoList | private List | mSystemViewInfoList |
Constructors Summary |
---|
public RenderSessionImpl(com.android.ide.common.rendering.api.SessionParams params)Creates a layout scene with all the information coming from the layout bridge API.
This must be followed by a call to {@link RenderSessionImpl#init(long)},
which act as a
call to {@link RenderSessionImpl#acquire(long)}
super(new SessionParams(params));
|
Methods Summary |
---|
private com.android.ide.common.rendering.api.Result | addView(android.view.ViewGroup parent, android.view.View view, int index)Adds a given view to a given parent at a given index.
try {
parent.addView(view, index);
return SUCCESS.createResult();
} catch (UnsupportedOperationException e) {
// looks like this is a view class that doesn't support children manipulation!
return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
}
| public com.android.ide.common.rendering.api.Result | animate(java.lang.Object targetObject, java.lang.String animationName, boolean isFrameworkAnimation, com.android.ide.common.rendering.api.IAnimationListener listener)Animate an object
{@link #acquire(long)} must have been called before this.
checkLock();
BridgeContext context = getContext();
// find the animation file.
ResourceValue animationResource;
int animationId = 0;
if (isFrameworkAnimation) {
animationResource = context.getRenderResources().getFrameworkResource(
ResourceType.ANIMATOR, animationName);
if (animationResource != null) {
animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
}
} else {
animationResource = context.getRenderResources().getProjectResource(
ResourceType.ANIMATOR, animationName);
if (animationResource != null) {
animationId = context.getProjectCallback().getResourceId(
ResourceType.ANIMATOR, animationName);
}
}
if (animationResource != null) {
try {
Animator anim = AnimatorInflater.loadAnimator(context, animationId);
if (anim != null) {
anim.setTarget(targetObject);
new PlayAnimationThread(anim, this, animationName, listener).start();
return SUCCESS.createResult();
}
} catch (Exception e) {
// get the real cause of the exception.
Throwable t = e;
while (t.getCause() != null) {
t = t.getCause();
}
return ERROR_UNKNOWN.createResult(t.getMessage(), t);
}
}
return ERROR_ANIM_NOT_FOUND.createResult();
| private com.android.layoutlib.bridge.bars.BridgeActionBar | createActionBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.ide.common.rendering.api.SessionParams params, android.view.ViewGroup parentView)Creates the action bar. Also queries the project callback for missing information.
if (mIsThemeAppCompat == Boolean.TRUE) {
return new AppCompatActionBar(context, params, parentView);
} else {
return new FrameworkActionBar(context, params, parentView);
}
| private com.android.layoutlib.bridge.bars.NavigationBar | createNavigationBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.resources.Density density, boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion)Creates the navigation bar with back, home and recent buttons.
NavigationBar navigationBar = new NavigationBar(context,
density, mNavigationBarOrientation, isRtl,
isRtlSupported, simulatedPlatformVersion);
if (mNavigationBarOrientation == LinearLayout.VERTICAL) {
navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize,
LayoutParams.MATCH_PARENT));
} else {
navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
mNavigationBarSize));
}
return navigationBar;
| private com.android.layoutlib.bridge.bars.StatusBar | createStatusBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.resources.Density density, int direction, boolean isRtlSupported, int platformVersion)Creates the status bar with wifi and battery icons.
StatusBar statusBar = new StatusBar(context, density,
direction, isRtlSupported, platformVersion);
statusBar.setLayoutParams(
new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, mStatusBarSize));
return statusBar;
| private com.android.layoutlib.bridge.bars.TitleBar | createTitleBar(com.android.layoutlib.bridge.android.BridgeContext context, java.lang.String title, int simulatedPlatformVersion)
TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
titleBar.setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize));
return titleBar;
| private com.android.ide.common.rendering.api.ViewInfo | createViewInfo(android.view.View view, int offset, boolean setExtendedInfo, boolean isContentFrame)Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
set.
if (view == null) {
return null;
}
ViewInfo result;
if (isContentFrame) {
// The view is part of the layout added by the user. Hence,
// the ViewCookie may be obtained only through the Context.
result = new ViewInfo(view.getClass().getName(),
getContext().getViewKey(view),
view.getLeft(), view.getTop() + offset, view.getRight(),
view.getBottom() + offset, view, view.getLayoutParams());
} else {
// We are part of the system decor.
SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
getViewKey(view),
view.getLeft(), view.getTop(), view.getRight(),
view.getBottom(), view, view.getLayoutParams());
result = r;
// We currently mark three kinds of views:
// 1. Menus in the Action Bar
// 2. Menus in the Overflow popup.
// 3. The overflow popup button.
if (view instanceof ListMenuItemView) {
// Mark 2.
// All menus in the popup are of type ListMenuItemView.
r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
} else {
// Mark 3.
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof ActionMenuView.LayoutParams &&
((ActionMenuView.LayoutParams) lp).isOverflowButton) {
r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
} else {
// Mark 1.
// A view is a menu in the Action Bar is it is not the overflow button and of
// its parent is of type ActionMenuView. We can also check if the view is
// instanceof ActionMenuItemView but that will fail for menus using
// actionProviderClass.
ViewParent parent = view.getParent();
while (parent != mViewRoot && parent instanceof ViewGroup) {
if (parent instanceof ActionMenuView) {
r.setViewType(ViewType.ACTION_BAR_MENU);
break;
}
parent = parent.getParent();
}
}
}
}
if (setExtendedInfo) {
MarginLayoutParams marginParams = null;
LayoutParams params = view.getLayoutParams();
if (params instanceof MarginLayoutParams) {
marginParams = (MarginLayoutParams) params;
}
result.setExtendedInfo(view.getBaseline(),
marginParams != null ? marginParams.leftMargin : 0,
marginParams != null ? marginParams.topMargin : 0,
marginParams != null ? marginParams.rightMargin : 0,
marginParams != null ? marginParams.bottomMargin : 0);
}
return result;
| private void | findActionBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)
if (mWindowIsFloating) {
return;
}
boolean windowActionBar = getBooleanThemeValue(resources,
"windowActionBar", true, !isThemeAppCompat(resources));
// if there's a value and it's false (default is true)
if (windowActionBar) {
// default size of the window title bar
mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT;
// get value from the theme.
ResourceValue value = resources.findItemInTheme("actionBarSize",
true /*isFrameworkAttr*/);
// resolve it
value = resources.resolveResValue(value);
if (value != null) {
// get the numerical value, if available
TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
true /*requireUnit*/);
if (typedValue != null) {
// compute the pixel value based on the display metrics
mActionBarSize = (int)typedValue.getDimension(metrics);
}
}
} else {
// action bar overrides title bar so only look for this one if action bar is hidden
boolean windowNoTitle = getBooleanThemeValue(resources,
"windowNoTitle", false, !isThemeAppCompat(resources));
if (!windowNoTitle) {
// default size of the window title bar
mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
// get value from the theme.
ResourceValue value = resources.findItemInTheme("windowTitleSize",
true /*isFrameworkAttr*/);
// resolve it
value = resources.resolveResValue(value);
if (value != null) {
// get the numerical value, if available
TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
value.getValue(), true /*requireUnit*/);
if (typedValue != null) {
// compute the pixel value based on the display metrics
mTitleBarSize = (int)typedValue.getDimension(metrics);
}
}
}
}
| private void | findBackground(com.android.ide.common.rendering.api.RenderResources resources)
if (!getParams().isBgColorOverridden()) {
mWindowBackground = resources.findItemInTheme("windowBackground",
true /*isFrameworkAttr*/);
if (mWindowBackground != null) {
mWindowBackground = resources.resolveResValue(mWindowBackground);
}
}
| private void | findNavigationBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)
if (hasSoftwareButtons() && !mWindowIsFloating) {
// default value
mNavigationBarSize = 48; // ??
HardwareConfig hardwareConfig = getParams().getHardwareConfig();
boolean barOnBottom = true;
if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
// compute the dp of the screen.
int shortSize = hardwareConfig.getScreenHeight();
// compute in dp
int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
hardwareConfig.getDensity().getDpiValue();
// 0-599dp: "phone" UI with bar on the side
// 600+dp: "tablet" UI with bar on the bottom
barOnBottom = shortSizeDp >= 600;
}
if (barOnBottom) {
mNavigationBarOrientation = LinearLayout.HORIZONTAL;
} else {
mNavigationBarOrientation = LinearLayout.VERTICAL;
}
// get the real value
ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
barOnBottom ? "navigation_bar_height" : "navigation_bar_width");
if (value != null) {
TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height",
value.getValue(), true /*requireUnit*/);
if (typedValue != null) {
// compute the pixel value based on the display metrics
mNavigationBarSize = (int)typedValue.getDimension(metrics);
}
}
}
| private void | findStatusBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)
boolean windowFullscreen = getBooleanThemeValue(resources,
"windowFullscreen", false, !isThemeAppCompat(resources));
if (!windowFullscreen && !mWindowIsFloating) {
// default value
mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
// get the real value
ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
"status_bar_height");
if (value != null) {
TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
value.getValue(), true /*requireUnit*/);
if (typedValue != null) {
// compute the pixel value based on the display metrics
mStatusBarSize = (int)typedValue.getDimension(metrics);
}
}
}
| private boolean | getBooleanThemeValue(com.android.ide.common.rendering.api.RenderResources resources, java.lang.String name, boolean defaultValue, boolean isFrameworkAttr)Looks for an attribute in the current theme.
ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);
// because it may reference something else, we resolve it.
value = resources.resolveResValue(value);
// if there's no value, return the default.
if (value == null || value.getValue() == null) {
return defaultValue;
}
return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
| public java.util.Map | getDefaultProperties(java.lang.Object viewObject)
return getContext().getDefaultPropMap(viewObject);
| public java.awt.image.BufferedImage | getImage()
return mImage;
| public com.android.ide.common.rendering.api.RenderSession | getSession()
return mScene;
| public java.util.List | getSystemViewInfos()
return mSystemViewInfoList;
| public java.util.List | getViewInfos()
return mViewInfoList;
| private java.lang.Object | getViewKey(android.view.View view)
BridgeContext context = getContext();
if (!(view instanceof MenuView.ItemView)) {
return context.getViewKey(view);
}
MenuItemImpl menuItem;
if (view instanceof ActionMenuItemView) {
menuItem = ((ActionMenuItemView) view).getItemData();
} else if (view instanceof ListMenuItemView) {
menuItem = ((ListMenuItemView) view).getItemData();
} else if (view instanceof IconMenuItemView) {
menuItem = ((IconMenuItemView) view).getItemData();
} else {
menuItem = null;
}
if (menuItem instanceof BridgeMenuItemImpl) {
return ((BridgeMenuItemImpl) menuItem).getViewCookie();
}
return null;
| private boolean | hasSoftwareButtons()
return getParams().getHardwareConfig().hasSoftwareButtons();
| public com.android.ide.common.rendering.api.Result | inflate()Inflates the layout.
{@link #acquire(long)} must have been called before this.
checkLock();
try {
SessionParams params = getParams();
HardwareConfig hardwareConfig = params.getHardwareConfig();
BridgeContext context = getContext();
boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
// the view group that receives the window background.
ViewGroup backgroundView;
if (mWindowIsFloating || params.isForceNoDecor()) {
backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
mViewRoot.setLayoutDirection(layoutDirection);
} else {
int simulatedPlatformVersion = params.getSimulatedPlatformVersion();
if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
/*
* This is a special case where the navigation bar is on the right.
+-------------------------------------------------+---+
| Status bar (always) | |
+-------------------------------------------------+ |
| (Layout with background drawable) | |
| +---------------------------------------------+ | |
| | Title/Action bar (optional) | | |
| +---------------------------------------------+ | |
| | Content, vertical extending | | |
| | | | |
| +---------------------------------------------+ | |
+-------------------------------------------------+---+
So we create a horizontal layout, with the nav bar on the right,
and the left part is the normal layout below without the nav bar at
the bottom
*/
LinearLayout topLayout = new LinearLayout(context);
topLayout.setLayoutDirection(layoutDirection);
mViewRoot = topLayout;
topLayout.setOrientation(LinearLayout.HORIZONTAL);
if (Config.showOnScreenNavBar(simulatedPlatformVersion)) {
try {
NavigationBar navigationBar = createNavigationBar(context,
hardwareConfig.getDensity(), isRtl, params.isRtlSupported(),
simulatedPlatformVersion);
topLayout.addView(navigationBar);
} catch (XmlPullParserException ignored) {
}
}
}
/*
* we're creating the following layout
*
+-------------------------------------------------+
| Status bar (always) |
+-------------------------------------------------+
| (Layout with background drawable) |
| +---------------------------------------------+ |
| | Title/Action bar (optional) | |
| +---------------------------------------------+ |
| | Content, vertical extending | |
| | | |
| +---------------------------------------------+ |
+-------------------------------------------------+
| Navigation bar for soft buttons, maybe see above|
+-------------------------------------------------+
*/
LinearLayout topLayout = new LinearLayout(context);
topLayout.setOrientation(LinearLayout.VERTICAL);
topLayout.setLayoutDirection(layoutDirection);
// if we don't already have a view root this is it
if (mViewRoot == null) {
mViewRoot = topLayout;
} else {
int topLayoutWidth =
params.getHardwareConfig().getScreenWidth() - mNavigationBarSize;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
topLayoutWidth, LayoutParams.MATCH_PARENT);
topLayout.setLayoutParams(layoutParams);
// this is the case of soft buttons + vertical bar.
// this top layout is the first layout in the horizontal layout. see above)
if (isRtl && params.isRtlSupported()) {
// If RTL is enabled, layoutlib will mirror the layouts. So, add the
// topLayout to the right of Navigation Bar and layoutlib will draw it
// to the left.
mViewRoot.addView(topLayout);
} else {
// Add the top layout to the left of the Navigation Bar.
mViewRoot.addView(topLayout, 0);
}
}
if (mStatusBarSize > 0) {
// system bar
try {
StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
layoutDirection, params.isRtlSupported(),
simulatedPlatformVersion);
topLayout.addView(statusBar);
} catch (XmlPullParserException ignored) {
}
}
LinearLayout backgroundLayout = new LinearLayout(context);
backgroundView = backgroundLayout;
backgroundLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0);
layoutParams.weight = 1;
backgroundLayout.setLayoutParams(layoutParams);
topLayout.addView(backgroundLayout);
// if the theme says no title/action bar, then the size will be 0
if (mActionBarSize > 0) {
BridgeActionBar actionBar = createActionBar(context, params, backgroundLayout);
actionBar.createMenuPopup();
mContentRoot = actionBar.getContentRoot();
} else if (mTitleBarSize > 0) {
try {
TitleBar titleBar = createTitleBar(context,
params.getAppLabel(),
simulatedPlatformVersion);
backgroundLayout.addView(titleBar);
} catch (XmlPullParserException ignored) {
}
}
// content frame
if (mContentRoot == null) {
mContentRoot = new FrameLayout(context);
layoutParams = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0);
layoutParams.weight = 1;
mContentRoot.setLayoutParams(layoutParams);
backgroundLayout.addView(mContentRoot);
}
if (Config.showOnScreenNavBar(simulatedPlatformVersion) &&
mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
mNavigationBarSize > 0) {
// system bar
try {
NavigationBar navigationBar = createNavigationBar(context,
hardwareConfig.getDensity(), isRtl, params.isRtlSupported(),
simulatedPlatformVersion);
topLayout.addView(navigationBar);
} catch (XmlPullParserException ignored) {
}
}
}
// Sets the project callback (custom view loader) to the fragment delegate so that
// it can instantiate the custom Fragment.
Fragment_Delegate.setProjectCallback(params.getProjectCallback());
String rootTag = params.getFlag(SessionParamsFlags.FLAG_KEY_ROOT_TAG);
boolean isPreference = "PreferenceScreen".equals(rootTag);
View view;
if (isPreference) {
view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
mContentRoot);
} else {
view = mInflater.inflate(mBlockParser, mContentRoot);
}
// done with the parser, pop it.
context.popParser();
Fragment_Delegate.setProjectCallback(null);
// set the AttachInfo on the root view.
AttachInfo_Accessor.setAttachInfo(mViewRoot);
// post-inflate process. For now this supports TabHost/TabWidget
postInflateProcess(view, params.getProjectCallback(), isPreference ? view : null);
// get the background drawable
if (mWindowBackground != null) {
Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
backgroundView.setBackground(d);
}
return SUCCESS.createResult();
} catch (PostInflateException e) {
return ERROR_INFLATION.createResult(e.getMessage(), e);
} catch (Throwable e) {
// get the real cause of the exception.
Throwable t = e;
while (t.getCause() != null) {
t = t.getCause();
}
return ERROR_INFLATION.createResult(t.getMessage(), t);
}
| public com.android.ide.common.rendering.api.Result | init(long timeout)Initializes and acquires the scene, creating various Android objects such as context,
inflater, and parser.
Result result = super.init(timeout);
if (!result.isSuccess()) {
return result;
}
SessionParams params = getParams();
BridgeContext context = getContext();
RenderResources resources = getParams().getResources();
DisplayMetrics metrics = getContext().getMetrics();
// use default of true in case it's not found to use alpha by default
mIsAlphaChannelImage = getBooleanThemeValue(resources, "windowIsFloating", true, true);
// FIXME: Find out why both variables are taking the same value.
mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", true, true);
findBackground(resources);
findStatusBar(resources, metrics);
findActionBar(resources, metrics);
findNavigationBar(resources, metrics);
// FIXME: find those out, and possibly add them to the render params
boolean hasNavigationBar = true;
//noinspection ConstantConditions
IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
metrics, Surface.ROTATION_0,
hasNavigationBar);
WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
// build the inflater and parser.
mInflater = new BridgeInflater(context, params.getProjectCallback());
context.setBridgeInflater(mInflater);
mBlockParser = new BridgeXmlBlockParser(
params.getLayoutDescription(), context, false /* platformResourceFlag */);
return SUCCESS.createResult();
| public com.android.ide.common.rendering.api.Result | insertChild(android.view.ViewGroup parentView, com.android.ide.common.rendering.api.ILayoutPullParser childXml, int index, com.android.ide.common.rendering.api.IAnimationListener listener)Insert a new child into an existing parent.
{@link #acquire(long)} must have been called before this.
checkLock();
BridgeContext context = getContext();
// create a block parser for the XML
BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
childXml, context, false /* platformResourceFlag */);
// inflate the child without adding it to the root since we want to control where it'll
// get added. We do pass the parentView however to ensure that the layoutParams will
// be created correctly.
final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
blockParser.ensurePopped();
invalidateRenderingSize();
if (listener != null) {
new AnimationThread(this, "insertChild", listener) {
@Override
public Result preAnimation() {
parentView.setLayoutTransition(new LayoutTransition());
return addView(parentView, child, index);
}
@Override
public void postAnimation() {
parentView.setLayoutTransition(null);
}
}.start();
// always return success since the real status will come through the listener.
return SUCCESS.createResult(child);
}
// add it to the parentView in the correct location
Result result = addView(parentView, child, index);
if (!result.isSuccess()) {
return result;
}
result = render(false /*freshRender*/);
if (result.isSuccess()) {
result = result.getCopyWithData(child);
}
return result;
| private void | invalidateRenderingSize()
mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
| public boolean | isAlphaChannelImage()
return mIsAlphaChannelImage;
| private boolean | isThemeAppCompat(com.android.ide.common.rendering.api.RenderResources resources)
// Ideally, we should check if the corresponding activity extends
// android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
if (mIsThemeAppCompat == null) {
StyleResourceValue defaultTheme = resources.getDefaultTheme();
// We can't simply check for parent using resources.themeIsParentOf() since the
// inheritance structure isn't really what one would expect. The first common parent
// between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
boolean isThemeAppCompat = false;
for (int i = 0; i < 50; i++) {
// for loop ensures that we don't run into cyclic theme inheritance.
if (defaultTheme.getName().startsWith("Theme.AppCompat")) {
isThemeAppCompat = true;
break;
}
defaultTheme = resources.getParent(defaultTheme);
if (defaultTheme == null) {
break;
}
}
mIsThemeAppCompat = isThemeAppCompat;
}
return mIsThemeAppCompat;
| private com.android.util.Pair | measureView(android.view.ViewGroup viewToMeasure, android.view.View measuredView, int width, int widthMode, int height, int heightMode)Executes {@link View#measure(int, int)} on a given view with the given parameters (used
to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
if measuredView is non null, the method returns a {@link Pair} of (width, height)
for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
viewToMeasure.measure(w_spec, h_spec);
if (measuredView != null) {
return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
}
return null;
| public com.android.ide.common.rendering.api.Result | moveChild(android.view.ViewGroup newParentView, android.view.View childView, int index, java.util.Map layoutParamsMap, com.android.ide.common.rendering.api.IAnimationListener listener)Moves a view to a new parent at a given location
{@link #acquire(long)} must have been called before this.
checkLock();
invalidateRenderingSize();
LayoutParams layoutParams = null;
if (layoutParamsMap != null) {
// need to create a new LayoutParams object for the new parent.
layoutParams = newParentView.generateLayoutParams(
new BridgeLayoutParamsMapAttributes(layoutParamsMap));
}
// get the current parent of the view that needs to be moved.
final ViewGroup previousParent = (ViewGroup) childView.getParent();
if (listener != null) {
final LayoutParams params = layoutParams;
// there is no support for animating views across layouts, so in case the new and old
// parent views are different we fake the animation through a no animation thread.
if (previousParent != newParentView) {
new Thread("not animated moveChild") {
@Override
public void run() {
Result result = moveView(previousParent, newParentView, childView, index,
params);
if (!result.isSuccess()) {
listener.done(result);
}
// ready to do the work, acquire the scene.
result = acquire(250);
if (!result.isSuccess()) {
listener.done(result);
return;
}
try {
result = render(false /*freshRender*/);
if (result.isSuccess()) {
listener.onNewFrame(RenderSessionImpl.this.getSession());
}
} finally {
release();
}
listener.done(result);
}
}.start();
} else {
new AnimationThread(this, "moveChild", listener) {
@Override
public Result preAnimation() {
// set up the transition for the parent.
LayoutTransition transition = new LayoutTransition();
previousParent.setLayoutTransition(transition);
// tweak the animation durations and start delays (to match the duration of
// animation playing just before).
// Note: Cannot user Animation.setDuration() directly. Have to set it
// on the LayoutTransition.
transition.setDuration(LayoutTransition.DISAPPEARING, 100);
// CHANGE_DISAPPEARING plays after DISAPPEARING
transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
// CHANGE_APPEARING plays after CHANGE_APPEARING
transition.setStartDelay(LayoutTransition.APPEARING, 100);
transition.setDuration(LayoutTransition.APPEARING, 100);
return moveView(previousParent, newParentView, childView, index, params);
}
@Override
public void postAnimation() {
previousParent.setLayoutTransition(null);
newParentView.setLayoutTransition(null);
}
}.start();
}
// always return success since the real status will come through the listener.
return SUCCESS.createResult(layoutParams);
}
Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
if (!result.isSuccess()) {
return result;
}
result = render(false /*freshRender*/);
if (layoutParams != null && result.isSuccess()) {
result = result.getCopyWithData(layoutParams);
}
return result;
| private com.android.ide.common.rendering.api.Result | moveView(android.view.ViewGroup previousParent, android.view.ViewGroup newParent, android.view.View movedView, int index, android.view.ViewGroup.LayoutParams params)Moves a View from its current parent to a new given parent at a new given location, with
an optional new {@link LayoutParams} instance
try {
// check if there is a transition on the previousParent.
LayoutTransition previousTransition = previousParent.getLayoutTransition();
if (previousTransition != null) {
// in this case there is an animation. This means we have to wait for the child's
// parent reference to be null'ed out so that we can add it to the new parent.
// It is technically removed right before the DISAPPEARING animation is done (if
// the animation of this type is not null, otherwise it's after which is impossible
// to handle).
// Because there is no move animation, if the new parent is the same as the old
// parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
// adding the child or the child will appear in its new location before the
// other children have made room for it.
// add a listener to the transition to be notified of the actual removal.
previousTransition.addTransitionListener(new TransitionListener() {
private int mChangeDisappearingCount = 0;
@Override
public void startTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType) {
if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
mChangeDisappearingCount++;
}
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType) {
if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
mChangeDisappearingCount--;
}
if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
mChangeDisappearingCount == 0) {
// add it to the parentView in the correct location
if (params != null) {
newParent.addView(movedView, index, params);
} else {
newParent.addView(movedView, index);
}
}
}
});
// remove the view from the current parent.
previousParent.removeView(movedView);
// and return since adding the view to the new parent is done in the listener.
return SUCCESS.createResult();
} else {
// standard code with no animation. pretty simple.
previousParent.removeView(movedView);
// add it to the parentView in the correct location
if (params != null) {
newParent.addView(movedView, index, params);
} else {
newParent.addView(movedView, index);
}
return SUCCESS.createResult();
}
} catch (UnsupportedOperationException e) {
// looks like this is a view class that doesn't support children manipulation!
return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
}
| private void | postInflateProcess(android.view.View view, com.android.ide.common.rendering.api.IProjectCallback projectCallback, android.view.View skip)Post process on a view hierarchy that was just inflated.
At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
{@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
based on the content of the {@link FrameLayout}.
if (view == skip) {
return;
}
if (view instanceof TabHost) {
setupTabHost((TabHost) view, projectCallback);
} else if (view instanceof QuickContactBadge) {
QuickContactBadge badge = (QuickContactBadge) view;
badge.setImageToDefault();
} else if (view instanceof AdapterView<?>) {
// get the view ID.
int id = view.getId();
BridgeContext context = getContext();
// get a ResourceReference from the integer ID.
ResourceReference listRef = context.resolveId(id);
if (listRef != null) {
SessionParams params = getParams();
AdapterBinding binding = params.getAdapterBindings().get(listRef);
// if there was no adapter binding, trying to get it from the call back.
if (binding == null) {
binding = params.getProjectCallback().getAdapterBinding(listRef,
context.getViewKey(view), view);
}
if (binding != null) {
if (view instanceof AbsListView) {
if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
view instanceof ListView) {
ListView list = (ListView) view;
boolean skipCallbackParser = false;
int count = binding.getHeaderCount();
for (int i = 0; i < count; i++) {
Pair<View, Boolean> pair = context.inflateView(
binding.getHeaderAt(i),
list, false /*attachToRoot*/, skipCallbackParser);
if (pair.getFirst() != null) {
list.addHeaderView(pair.getFirst());
}
skipCallbackParser |= pair.getSecond();
}
count = binding.getFooterCount();
for (int i = 0; i < count; i++) {
Pair<View, Boolean> pair = context.inflateView(
binding.getFooterAt(i),
list, false /*attachToRoot*/, skipCallbackParser);
if (pair.getFirst() != null) {
list.addFooterView(pair.getFirst());
}
skipCallbackParser |= pair.getSecond();
}
}
if (view instanceof ExpandableListView) {
((ExpandableListView) view).setAdapter(
new FakeExpandableAdapter(
listRef, binding, params.getProjectCallback()));
} else {
((AbsListView) view).setAdapter(
new FakeAdapter(
listRef, binding, params.getProjectCallback()));
}
} else if (view instanceof AbsSpinner) {
((AbsSpinner) view).setAdapter(
new FakeAdapter(
listRef, binding, params.getProjectCallback()));
}
}
}
} else if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
final int count = group.getChildCount();
for (int c = 0; c < count; c++) {
View child = group.getChildAt(c);
postInflateProcess(child, projectCallback, skip);
}
}
| public com.android.ide.common.rendering.api.Result | removeChild(android.view.View childView, com.android.ide.common.rendering.api.IAnimationListener listener)Removes a child from its current parent.
{@link #acquire(long)} must have been called before this.
checkLock();
invalidateRenderingSize();
final ViewGroup parent = (ViewGroup) childView.getParent();
if (listener != null) {
new AnimationThread(this, "moveChild", listener) {
@Override
public Result preAnimation() {
parent.setLayoutTransition(new LayoutTransition());
return removeView(parent, childView);
}
@Override
public void postAnimation() {
parent.setLayoutTransition(null);
}
}.start();
// always return success since the real status will come through the listener.
return SUCCESS.createResult();
}
Result result = removeView(parent, childView);
if (!result.isSuccess()) {
return result;
}
return render(false /*freshRender*/);
| private com.android.ide.common.rendering.api.Result | removeView(android.view.ViewGroup parent, android.view.View view)Removes a given view from its current parent.
try {
parent.removeView(view);
return SUCCESS.createResult();
} catch (UnsupportedOperationException e) {
// looks like this is a view class that doesn't support children manipulation!
return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
}
| public com.android.ide.common.rendering.api.Result | render(boolean freshRender)Renders the scene.
{@link #acquire(long)} must have been called before this.
checkLock();
SessionParams params = getParams();
try {
if (mViewRoot == null) {
return ERROR_NOT_INFLATED.createResult();
}
RenderingMode renderingMode = params.getRenderingMode();
HardwareConfig hardwareConfig = params.getHardwareConfig();
// only do the screen measure when needed.
boolean newRenderSize = false;
if (mMeasuredScreenWidth == -1) {
newRenderSize = true;
mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
if (renderingMode != RenderingMode.NORMAL) {
int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
: MeasureSpec.EXACTLY;
int heightMeasureSpecMode = renderingMode.isVertExpand() ?
MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
: MeasureSpec.EXACTLY;
// We used to compare the measured size of the content to the screen size but
// this does not work anymore due to the 2 following issues:
// - If the content is in a decor (system bar, title/action bar), the root view
// will not resize even with the UNSPECIFIED because of the embedded layout.
// - If there is no decor, but a dialog frame, then the dialog padding prevents
// comparing the size of the content to the screen frame (as it would not
// take into account the dialog padding).
// The solution is to first get the content size in a normal rendering, inside
// the decor or the dialog padding.
// Then measure only the content with UNSPECIFIED to see the size difference
// and apply this to the screen size.
// first measure the full layout, with EXACTLY to get the size of the
// content as it is inside the decor/dialog
@SuppressWarnings("deprecation")
Pair<Integer, Integer> exactMeasure = measureView(
mViewRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
// now measure the content only using UNSPECIFIED (where applicable, based on
// the rendering mode). This will give us the size the content needs.
@SuppressWarnings("deprecation")
Pair<Integer, Integer> result = measureView(
mContentRoot, mContentRoot.getChildAt(0),
mMeasuredScreenWidth, widthMeasureSpecMode,
mMeasuredScreenHeight, heightMeasureSpecMode);
// now look at the difference and add what is needed.
if (renderingMode.isHorizExpand()) {
int measuredWidth = exactMeasure.getFirst();
int neededWidth = result.getFirst();
if (neededWidth > measuredWidth) {
mMeasuredScreenWidth += neededWidth - measuredWidth;
}
}
if (renderingMode.isVertExpand()) {
int measuredHeight = exactMeasure.getSecond();
int neededHeight = result.getSecond();
if (neededHeight > measuredHeight) {
mMeasuredScreenHeight += neededHeight - measuredHeight;
}
}
}
}
// measure again with the size we need
// This must always be done before the call to layout
measureView(mViewRoot, null /*measuredView*/,
mMeasuredScreenWidth, MeasureSpec.EXACTLY,
mMeasuredScreenHeight, MeasureSpec.EXACTLY);
// now do the layout.
mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
if (params.isLayoutOnly()) {
// delete the canvas and image to reset them on the next full rendering
mImage = null;
mCanvas = null;
} else {
AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
// draw the views
// create the BufferedImage into which the layout will be rendered.
boolean newImage = false;
if (newRenderSize || mCanvas == null) {
if (params.getImageFactory() != null) {
mImage = params.getImageFactory().getImage(
mMeasuredScreenWidth,
mMeasuredScreenHeight);
} else {
mImage = new BufferedImage(
mMeasuredScreenWidth,
mMeasuredScreenHeight,
BufferedImage.TYPE_INT_ARGB);
newImage = true;
}
if (params.isBgColorOverridden()) {
// since we override the content, it's the same as if it was a new image.
newImage = true;
Graphics2D gc = mImage.createGraphics();
gc.setColor(new Color(params.getOverrideBgColor(), true));
gc.setComposite(AlphaComposite.Src);
gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
gc.dispose();
}
// create an Android bitmap around the BufferedImage
Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
true /*isMutable*/, hardwareConfig.getDensity());
// create a Canvas around the Android bitmap
mCanvas = new Canvas(bitmap);
mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
}
if (freshRender && !newImage) {
Graphics2D gc = mImage.createGraphics();
gc.setComposite(AlphaComposite.Src);
gc.setColor(new Color(0x00000000, true));
gc.fillRect(0, 0,
mMeasuredScreenWidth, mMeasuredScreenHeight);
// done
gc.dispose();
}
mViewRoot.draw(mCanvas);
}
mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
false);
// success!
return SUCCESS.createResult();
} catch (Throwable e) {
// get the real cause of the exception.
Throwable t = e;
while (t.getCause() != null) {
t = t.getCause();
}
return ERROR_UNKNOWN.createResult(t.getMessage(), t);
}
| public void | setScene(com.android.ide.common.rendering.api.RenderSession session)
mScene = session;
| private void | setupTabHost(android.widget.TabHost tabHost, com.android.ide.common.rendering.api.IProjectCallback projectCallback)Sets up a {@link TabHost} object.
// look for the TabWidget, and the FrameLayout. They have their own specific names
View v = tabHost.findViewById(android.R.id.tabs);
if (v == null) {
throw new PostInflateException(
"TabHost requires a TabWidget with id \"android:id/tabs\".\n");
}
if (!(v instanceof TabWidget)) {
throw new PostInflateException(String.format(
"TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
"View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
}
v = tabHost.findViewById(android.R.id.tabcontent);
if (v == null) {
// TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
//noinspection SpellCheckingInspection
throw new PostInflateException(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
}
if (!(v instanceof FrameLayout)) {
//noinspection SpellCheckingInspection
throw new PostInflateException(String.format(
"TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
"View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
}
FrameLayout content = (FrameLayout)v;
// now process the content of the frameLayout and dynamically create tabs for it.
final int count = content.getChildCount();
// this must be called before addTab() so that the TabHost searches its TabWidget
// and FrameLayout.
tabHost.setup();
if (count == 0) {
// Create a dummy child to get a single tab
TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label",
tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details))
.setContent(new TabHost.TabContentFactory() {
@Override
public View createTabContent(String tag) {
return new LinearLayout(getContext());
}
});
tabHost.addTab(spec);
} else {
// for each child of the frameLayout, add a new TabSpec
for (int i = 0 ; i < count ; i++) {
View child = content.getChildAt(i);
String tabSpec = String.format("tab_spec%d", i+1);
@SuppressWarnings("ConstantConditions") // child cannot be null.
int id = child.getId();
@SuppressWarnings("deprecation")
Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
String name;
if (resource != null) {
name = resource.getSecond();
} else {
name = String.format("Tab %d", i+1); // default name if id is unresolved.
}
tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
}
}
| private com.android.ide.common.rendering.api.ViewInfo | visit(android.view.View view, int offset, boolean setExtendedInfo, boolean isContentFrame)Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
bounds of all the views.
ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
if (view instanceof ViewGroup) {
ViewGroup group = ((ViewGroup) view);
result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
setExtendedInfo, isContentFrame));
}
return result;
| private java.util.List | visitAllChildren(android.view.ViewGroup viewGroup, int offset, boolean setExtendedInfo, boolean isContentFrame)Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
the children of the {@code mContentRoot}.
if (viewGroup == null) {
return null;
}
if (!isContentFrame) {
offset += viewGroup.getTop();
}
int childCount = viewGroup.getChildCount();
if (viewGroup == mContentRoot) {
List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
setExtendedInfo);
childrenWithoutOffset.add(childViewInfo[0]);
childrenWithOffset.add(childViewInfo[1]);
}
mViewInfoList = childrenWithOffset;
return childrenWithoutOffset;
} else {
List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
for (int i = 0; i < childCount; i++) {
children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
isContentFrame));
}
return children;
}
| private com.android.ide.common.rendering.api.ViewInfo[] | visitContentRoot(android.view.View view, int offset, boolean setExtendedInfo)Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
one with the {@code offset} and other without the {@code offset}. The offset is needed to
get the right bounds if the {@code ViewInfo} hierarchy is accessed from
{@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
offset is not needed.
ViewInfo[] result = new ViewInfo[2];
if (view == null) {
return result;
}
result[0] = createViewInfo(view, 0, setExtendedInfo, true);
result[1] = createViewInfo(view, offset, setExtendedInfo, true);
if (view instanceof ViewGroup) {
List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
result[0].setChildren(children);
result[1].setChildren(children);
}
return result;
|
|