1 /*
2  * Copyright (C) 2014 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.contacts.util;
18 
19 import android.app.Activity;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.os.Trace;
25 import androidx.core.content.ContextCompat;
26 
27 import com.android.contacts.R;
28 import com.android.contacts.activities.PeopleActivity;
29 
30 public class MaterialColorMapUtils {
31     private final TypedArray sPrimaryColors;
32     private final TypedArray sSecondaryColors;
33 
MaterialColorMapUtils(Resources resources)34     public MaterialColorMapUtils(Resources resources) {
35         sPrimaryColors = resources.obtainTypedArray(
36                 com.android.contacts.R.array.letter_tile_colors);
37         sSecondaryColors = resources.obtainTypedArray(
38                 com.android.contacts.R.array.letter_tile_colors_dark);
39     }
40 
41     public static class MaterialPalette implements Parcelable {
MaterialPalette(int primaryColor, int secondaryColor)42         public MaterialPalette(int primaryColor, int secondaryColor) {
43             mPrimaryColor = primaryColor;
44             mSecondaryColor = secondaryColor;
45         }
46         public final int mPrimaryColor;
47         public final int mSecondaryColor;
48 
49         @Override
equals(Object obj)50         public boolean equals(Object obj) {
51             if (this == obj) {
52                 return true;
53             }
54             if (obj == null) {
55                 return false;
56             }
57             if (getClass() != obj.getClass()) {
58                 return false;
59             }
60             MaterialPalette other = (MaterialPalette) obj;
61             if (mPrimaryColor != other.mPrimaryColor) {
62                 return false;
63             }
64             if (mSecondaryColor != other.mSecondaryColor) {
65                 return false;
66             }
67             return true;
68         }
69 
70         @Override
hashCode()71         public int hashCode() {
72             final int prime = 31;
73             int result = 1;
74             result = prime * result + mPrimaryColor;
75             result = prime * result + mSecondaryColor;
76             return result;
77         }
78 
79         @Override
describeContents()80         public int describeContents() {
81             return 0;
82         }
83 
84         @Override
writeToParcel(Parcel dest, int flags)85         public void writeToParcel(Parcel dest, int flags) {
86             dest.writeInt(mPrimaryColor);
87             dest.writeInt(mSecondaryColor);
88         }
89 
MaterialPalette(Parcel in)90         private MaterialPalette(Parcel in) {
91             mPrimaryColor = in.readInt();
92             mSecondaryColor = in.readInt();
93         }
94 
95         public static final Creator<MaterialPalette> CREATOR = new Creator<MaterialPalette>() {
96                 @Override
97                 public MaterialPalette createFromParcel(Parcel in) {
98                     return new MaterialPalette(in);
99                 }
100 
101                 @Override
102                 public MaterialPalette[] newArray(int size) {
103                     return new MaterialPalette[size];
104                 }
105         };
106     }
107 
108     /**
109      * Return primary and secondary colors from the Material color palette that are similar to
110      * {@param color}.
111      */
calculatePrimaryAndSecondaryColor(int color)112     public MaterialPalette calculatePrimaryAndSecondaryColor(int color) {
113         Trace.beginSection("calculatePrimaryAndSecondaryColor");
114 
115         final float colorHue = hue(color);
116         float minimumDistance = Float.MAX_VALUE;
117         int indexBestMatch = 0;
118         for (int i = 0; i < sPrimaryColors.length(); i++) {
119             final int primaryColor = sPrimaryColors.getColor(i, 0);
120             final float comparedHue = hue(primaryColor);
121             // No need to be perceptually accurate when calculating color distances since
122             // we are only mapping to 15 colors. Being slightly inaccurate isn't going to change
123             // the mapping very often.
124             final float distance = Math.abs(comparedHue - colorHue);
125             if (distance < minimumDistance) {
126                 minimumDistance = distance;
127                 indexBestMatch = i;
128             }
129         }
130 
131         Trace.endSection();
132         return new MaterialPalette(sPrimaryColors.getColor(indexBestMatch, 0),
133                 sSecondaryColors.getColor(indexBestMatch, 0));
134     }
135 
getDefaultPrimaryAndSecondaryColors(Resources resources)136     public static MaterialPalette getDefaultPrimaryAndSecondaryColors(Resources resources) {
137         final int primaryColor = resources.getColor(
138                 R.color.quickcontact_default_photo_tint_color);
139         final int secondaryColor = resources.getColor(
140                 R.color.quickcontact_default_photo_tint_color_dark);
141         return new MaterialPalette(primaryColor, secondaryColor);
142     }
143 
144     /**
145      * Returns the hue component of a color int.
146      *
147      * @return A value between 0.0f and 1.0f
148      */
hue(int color)149     public static float hue(int color) {
150         int r = (color >> 16) & 0xFF;
151         int g = (color >> 8) & 0xFF;
152         int b = color & 0xFF;
153 
154         int V = Math.max(b, Math.max(r, g));
155         int temp = Math.min(b, Math.min(r, g));
156 
157         float H;
158 
159         if (V == temp) {
160             H = 0;
161         } else {
162             final float vtemp = V - temp;
163             final float cr = (V - r) / vtemp;
164             final float cg = (V - g) / vtemp;
165             final float cb = (V - b) / vtemp;
166 
167             if (r == V) {
168                 H = cb - cg;
169             } else if (g == V) {
170                 H = 2 + cr - cb;
171             } else {
172                 H = 4 + cg - cr;
173             }
174 
175             H /= 6.f;
176             if (H < 0) {
177                 H++;
178             }
179         }
180 
181         return H;
182     }
183 
184     /**
185      * Returns status bar color for group view and non-group views.
186      */
getStatusBarColor(Activity activity)187     public static int getStatusBarColor(Activity activity) {
188         final boolean isGroupView = activity instanceof PeopleActivity
189                 && ((PeopleActivity) activity).isGroupView();
190         return isGroupView
191                 ? ContextCompat.getColor(activity, R.color.group_primary_color_dark)
192                 : ContextCompat.getColor(activity, R.color.primary_color_dark);
193     }
194 
195     /**
196      * Returns toolbar color for group view and non-group views.
197      */
getToolBarColor(Activity activity)198     public static int getToolBarColor(Activity activity) {
199         final boolean isGroupView = activity instanceof PeopleActivity
200                 && ((PeopleActivity) activity).isGroupView();
201         return isGroupView
202                 ? ContextCompat.getColor(activity, R.color.group_primary_color)
203                 : ContextCompat.getColor(activity, R.color.primary_color);
204     }
205 }
206