blob: 1468b27822a1b040176afad09a4b7c9ae10a5961 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.icons;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo.Extender;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SafeCloseable;
import java.util.Calendar;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/**
* Class to handle icon loading from different packages
*/
public class IconProvider {
private static final String TAG = "IconProvider";
private static final boolean DEBUG = false;
private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
private static final String SYSTEM_STATE_SEPARATOR = " ";
// Default value returned if there are problems getting resources.
private static final int NO_ID = 0;
private static final BiFunction<LauncherActivityInfo, Integer, Drawable> LAI_LOADER =
LauncherActivityInfo::getIcon;
private static final BiFunction<ActivityInfo, PackageManager, Drawable> AI_LOADER =
ActivityInfo::loadUnbadgedIcon;
private final Context mContext;
private final ComponentName mCalendar;
private final ComponentName mClock;
public IconProvider(Context context) {
mContext = context;
mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
mClock = parseComponentOrNull(context, R.string.clock_component_name);
}
/**
* Adds any modification to the provided systemState for dynamic icons. This system state
* is used by caches to check for icon invalidation.
*/
public String getSystemStateForPackage(String systemState, String packageName) {
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
return systemState + SYSTEM_STATE_SEPARATOR + getDay();
} else {
return systemState;
}
}
/**
* Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly
* on the UI
*/
public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) {
Drawable icon = getIcon(info, iconDpi);
if (icon instanceof BitmapInfo.Extender) {
((Extender) icon).prepareToDrawOnUi();
}
return icon;
}
/**
* Loads the icon for the provided LauncherActivityInfo
*/
public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
return getIcon(info.getApplicationInfo().packageName, info.getUser(),
info, iconDpi, LAI_LOADER);
}
/**
* Loads the icon for the provided activity info
*/
public Drawable getIcon(ActivityInfo info, UserHandle user) {
return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(),
AI_LOADER);
}
private <T, P> Drawable getIcon(String packageName, UserHandle user, T obj, P param,
BiFunction<T, P, Drawable> loader) {
Drawable icon = null;
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
icon = loadCalendarDrawable(0);
} else if (mClock != null
&& mClock.getPackageName().equals(packageName)
&& Process.myUserHandle().equals(user)) {
icon = loadClockDrawable(0);
}
return icon == null ? loader.apply(obj, param) : icon;
}
private Drawable loadCalendarDrawable(int iconDpi) {
PackageManager pm = mContext.getPackageManager();
try {
final Bundle metadata = pm.getActivityInfo(
mCalendar,
PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
.metaData;
final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
final int id = getDynamicIconId(metadata, resources);
if (id != NO_ID) {
if (DEBUG) Log.d(TAG, "Got icon #" + id);
return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
}
} catch (PackageManager.NameNotFoundException e) {
if (DEBUG) {
Log.d(TAG, "Could not get activityinfo or resources for package: "
+ mCalendar.getPackageName());
}
}
return null;
}
private Drawable loadClockDrawable(int iconDpi) {
return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
}
protected boolean isClockIcon(ComponentKey key) {
return mClock != null && mClock.equals(key.componentName)
&& Process.myUserHandle().equals(key.user);
}
/**
* @param metadata metadata of the default activity of Calendar
* @param resources from the Calendar package
* @return the resource id for today's Calendar icon; 0 if resources cannot be found.
*/
private int getDynamicIconId(Bundle metadata, Resources resources) {
if (metadata == null) {
return NO_ID;
}
String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
final int arrayId = metadata.getInt(key, NO_ID);
if (arrayId == NO_ID) {
return NO_ID;
}
try {
return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
} catch (Resources.NotFoundException e) {
if (DEBUG) {
Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
}
return NO_ID;
}
}
/**
* @return Today's day of the month, zero-indexed.
*/
private int getDay() {
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
}
/**
* Registers a callback to listen for calendar icon changes.
* The callback receives the packageName for the calendar icon
*/
public static SafeCloseable registerIconChangeListener(Context context,
BiConsumer<String, UserHandle> callback, Handler handler) {
ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
if (calendar == null && clock == null) {
return () -> { };
}
BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
if (calendar != null) {
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_DATE_CHANGED);
}
context.registerReceiver(receiver, filter, null, handler);
return () -> context.unregisterReceiver(receiver);
}
private static class DateTimeChangeReceiver extends BroadcastReceiver {
private final BiConsumer<String, UserHandle> mCallback;
DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
mCallback = callback;
}
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
if (clock != null) {
mCallback.accept(clock.getPackageName(), Process.myUserHandle());
}
}
ComponentName calendar =
parseComponentOrNull(context, R.string.calendar_component_name);
if (calendar != null) {
for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
mCallback.accept(calendar.getPackageName(), user);
}
}
}
}
private static ComponentName parseComponentOrNull(Context context, int resId) {
String cn = context.getString(resId);
return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
}
}