/*
* Copyright (C) 2014 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.systemui.qs.tiles;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.qs.QSTile;
import java.util.Arrays;
import java.util.Objects;
public class IntentTile extends QSTile<QSTile.State> {
public static final String PREFIX = "intent(";
private PendingIntent mOnClick;
private String mOnClickUri;
private PendingIntent mOnLongClick;
private String mOnLongClickUri;
private int mCurrentUserId;
private String mIntentPackage;
private Intent mLastIntent;
private IntentTile(Host host, String action) {
super(host);
mContext.registerReceiver(mReceiver, new IntentFilter(action));
}
@Override
protected void handleDestroy() {
super.handleDestroy();
mContext.unregisterReceiver(mReceiver);
}
public static QSTile<?> create(Host host, String spec) {
if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
throw new IllegalArgumentException("Bad intent tile spec: " + spec);
}
final String action = spec.substring(PREFIX.length(), spec.length() - 1);
if (action.isEmpty()) {
throw new IllegalArgumentException("Empty intent tile spec action");
}
return new IntentTile(host, action);
}
@Override
public void setListening(boolean listening) {
}
@Override
protected State newTileState() {
return new State();
}
@Override
protected void handleUserSwitch(int newUserId) {
super.handleUserSwitch(newUserId);
mCurrentUserId = newUserId;
}
@Override
protected void handleClick() {
MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
sendIntent("click", mOnClick, mOnClickUri);
}
@Override
protected void handleLongClick() {
sendIntent("long-click", mOnLongClick, mOnLongClickUri);
}
private void sendIntent(String type, PendingIntent pi, String uri) {
try {
if (pi != null) {
if (pi.isActivity()) {
getHost().startActivityDismissingKeyguard(pi.getIntent());
} else {
pi.send();
}
} else if (uri != null) {
final Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
} catch (Throwable t) {
Log.w(TAG, "Error sending " + type + " intent", t);
}
}
@Override
protected void handleUpdateState(State state, Object arg) {
Intent intent = (Intent) arg;
if (intent == null) {
if (mLastIntent == null) {
return;
}
// No intent but need to refresh state, just use the last one.
intent = mLastIntent;
}
// Save the last one in case we need it later.
mLastIntent = intent;
state.visible = intent.getBooleanExtra("visible", true);
state.contentDescription = intent.getStringExtra("contentDescription");
state.label = intent.getStringExtra("label");
state.icon = null;
final byte[] iconBitmap = intent.getByteArrayExtra("iconBitmap");
if (iconBitmap != null) {
try {
state.icon = new BytesIcon(iconBitmap);
} catch (Throwable t) {
Log.w(TAG, "Error loading icon bitmap, length " + iconBitmap.length, t);
}
} else {
final int iconId = intent.getIntExtra("iconId", 0);
if (iconId != 0) {
final String iconPackage = intent.getStringExtra("iconPackage");
if (!TextUtils.isEmpty(iconPackage)) {
state.icon = new PackageDrawableIcon(iconPackage, iconId);
} else {
state.icon = ResourceIcon.get(iconId);
}
}
}
mOnClick = intent.getParcelableExtra("onClick");
mOnClickUri = intent.getStringExtra("onClickUri");
mOnLongClick = intent.getParcelableExtra("onLongClick");
mOnLongClickUri = intent.getStringExtra("onLongClickUri");
mIntentPackage = intent.getStringExtra("package");
mIntentPackage = mIntentPackage == null ? "" : mIntentPackage;
}
@Override
public int getMetricsCategory() {
return MetricsLogger.QS_INTENT;
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refreshState(intent);
}
};
private static class BytesIcon extends Icon {
private final byte[] mBytes;
public BytesIcon(byte[] bytes) {
mBytes = bytes;
}
@Override
public Drawable getDrawable(Context context) {
final Bitmap b = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
return new BitmapDrawable(context.getResources(), b);
}
@Override
public boolean equals(Object o) {
return o instanceof BytesIcon && Arrays.equals(((BytesIcon) o).mBytes, mBytes);
}
@Override
public String toString() {
return String.format("BytesIcon[len=%s]", mBytes.length);
}
}
private class PackageDrawableIcon extends Icon {
private final String mPackage;
private final int mResId;
public PackageDrawableIcon(String pkg, int resId) {
mPackage = pkg;
mResId = resId;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof PackageDrawableIcon)) return false;
final PackageDrawableIcon other = (PackageDrawableIcon) o;
return Objects.equals(other.mPackage, mPackage) && other.mResId == mResId;
}
@Override
public Drawable getDrawable(Context context) {
try {
return context.createPackageContext(mPackage, 0).getDrawable(mResId);
} catch (Throwable t) {
Log.w(TAG, "Error loading package drawable pkg=" + mPackage + " id=" + mResId, t);
return null;
}
}
@Override
public String toString() {
return String.format("PackageDrawableIcon[pkg=%s,id=0x%08x]", mPackage, mResId);
}
}
}