1 /*
2  * Copyright (C) 2016 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.google.android.setupdesign.view;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.os.Build.VERSION_CODES;
23 import android.util.AttributeSet;
24 import android.view.ViewGroup;
25 import android.widget.FrameLayout;
26 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
27 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
28 import com.google.android.setupcompat.util.BuildCompatUtils;
29 import com.google.android.setupdesign.R;
30 
31 /**
32  * A FrameLayout subclass that has an "intrinsic size", which is the size it wants to be if that is
33  * within the constraints given by the parent. The intrinsic size can be set with the {@code
34  * android:width} and {@code android:height} attributes in XML.
35  *
36  * <p>Note that for the intrinsic size to be meaningful, {@code android:layout_width} and/or {@code
37  * android:layout_height} will need to be {@code wrap_content}.
38  */
39 public class IntrinsicSizeFrameLayout extends FrameLayout {
40 
41   private int intrinsicHeight = 0;
42   private int intrinsicWidth = 0;
43 
IntrinsicSizeFrameLayout(Context context)44   public IntrinsicSizeFrameLayout(Context context) {
45     super(context);
46     init(context, null, 0);
47   }
48 
IntrinsicSizeFrameLayout(Context context, AttributeSet attrs)49   public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs) {
50     super(context, attrs);
51     init(context, attrs, 0);
52   }
53 
54   @TargetApi(VERSION_CODES.HONEYCOMB)
IntrinsicSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr)55   public IntrinsicSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
56     super(context, attrs, defStyleAttr);
57     init(context, attrs, defStyleAttr);
58   }
59 
init(Context context, AttributeSet attrs, int defStyleAttr)60   private void init(Context context, AttributeSet attrs, int defStyleAttr) {
61     if (isInEditMode()) {
62       return;
63     }
64 
65     final TypedArray a =
66         context.obtainStyledAttributes(
67             attrs, R.styleable.SudIntrinsicSizeFrameLayout, defStyleAttr, 0);
68     intrinsicHeight =
69         a.getDimensionPixelSize(R.styleable.SudIntrinsicSizeFrameLayout_android_height, 0);
70     intrinsicWidth =
71         a.getDimensionPixelSize(R.styleable.SudIntrinsicSizeFrameLayout_android_width, 0);
72     a.recycle();
73 
74     if (BuildCompatUtils.isAtLeastS()) {
75       if (PartnerConfigHelper.get(context)
76           .isPartnerConfigAvailable(PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_HEIGHT)) {
77         intrinsicHeight =
78             (int)
79                 PartnerConfigHelper.get(context)
80                     .getDimension(context, PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_HEIGHT);
81       }
82       if (PartnerConfigHelper.get(context)
83           .isPartnerConfigAvailable(PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_WIDTH)) {
84         intrinsicWidth =
85             (int)
86                 PartnerConfigHelper.get(context)
87                     .getDimension(context, PartnerConfig.CONFIG_CARD_VIEW_INTRINSIC_WIDTH);
88       }
89     }
90   }
91 
92   @Override
setLayoutParams(ViewGroup.LayoutParams params)93   public void setLayoutParams(ViewGroup.LayoutParams params) {
94     if (BuildCompatUtils.isAtLeastS()) {
95       // When both intrinsic height and width are 0, the card view style would be removed from
96       // foldable/tablet layout. It must set the layout width and height to MATCH_PARENT and then it
97       // can ignore the IntrinsicSizeFrameLayout from the foldable/tablet layout.
98       if (intrinsicHeight == 0 && intrinsicWidth == 0) {
99         params.width = ViewGroup.LayoutParams.MATCH_PARENT;
100         params.height = ViewGroup.LayoutParams.MATCH_PARENT;
101       }
102     }
103     super.setLayoutParams(params);
104   }
105 
106   @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)107   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
108     super.onMeasure(
109         getIntrinsicMeasureSpec(widthMeasureSpec, intrinsicWidth),
110         getIntrinsicMeasureSpec(heightMeasureSpec, intrinsicHeight));
111   }
112 
getIntrinsicMeasureSpec(int measureSpec, int intrinsicSize)113   private int getIntrinsicMeasureSpec(int measureSpec, int intrinsicSize) {
114     if (intrinsicSize <= 0) {
115       // Intrinsic size is not set, just return the original spec
116       return measureSpec;
117     }
118     final int mode = MeasureSpec.getMode(measureSpec);
119     final int size = MeasureSpec.getSize(measureSpec);
120     if (mode == MeasureSpec.UNSPECIFIED) {
121       // Parent did not give any constraint, so we'll be the intrinsic size
122       return MeasureSpec.makeMeasureSpec(intrinsicHeight, MeasureSpec.EXACTLY);
123     } else if (mode == MeasureSpec.AT_MOST) {
124       // If intrinsic size is within parents constraint, take the intrinsic size.
125       // Otherwise take the parents size because that's closest to the intrinsic size.
126       return MeasureSpec.makeMeasureSpec(Math.min(size, intrinsicHeight), MeasureSpec.EXACTLY);
127     }
128     // Parent specified EXACTLY, or in all other cases, just return the original spec
129     return measureSpec;
130   }
131 }
132