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