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 17package com.android.systemui.qs.tiles; 18 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.graphics.Bitmap; 25import android.graphics.BitmapFactory; 26import android.graphics.drawable.BitmapDrawable; 27import android.graphics.drawable.Drawable; 28import android.os.UserHandle; 29import android.text.TextUtils; 30import android.util.Log; 31 32import com.android.internal.logging.MetricsLogger; 33import com.android.internal.logging.MetricsProto.MetricsEvent; 34import com.android.systemui.qs.QSTile; 35 36import java.util.Arrays; 37import java.util.Objects; 38 39public class IntentTile extends QSTile<QSTile.State> { 40 public static final String PREFIX = "intent("; 41 42 private PendingIntent mOnClick; 43 private String mOnClickUri; 44 private PendingIntent mOnLongClick; 45 private String mOnLongClickUri; 46 private int mCurrentUserId; 47 private String mIntentPackage; 48 49 private Intent mLastIntent; 50 51 private IntentTile(Host host, String action) { 52 super(host); 53 mContext.registerReceiver(mReceiver, new IntentFilter(action)); 54 } 55 56 @Override 57 protected void handleDestroy() { 58 super.handleDestroy(); 59 mContext.unregisterReceiver(mReceiver); 60 } 61 62 public static QSTile<?> create(Host host, String spec) { 63 if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) { 64 throw new IllegalArgumentException("Bad intent tile spec: " + spec); 65 } 66 final String action = spec.substring(PREFIX.length(), spec.length() - 1); 67 if (action.isEmpty()) { 68 throw new IllegalArgumentException("Empty intent tile spec action"); 69 } 70 return new IntentTile(host, action); 71 } 72 73 @Override 74 public void setListening(boolean listening) { 75 } 76 77 @Override 78 public State newTileState() { 79 return new State(); 80 } 81 82 @Override 83 protected void handleUserSwitch(int newUserId) { 84 super.handleUserSwitch(newUserId); 85 mCurrentUserId = newUserId; 86 } 87 88 @Override 89 protected void handleClick() { 90 MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage); 91 sendIntent("click", mOnClick, mOnClickUri); 92 } 93 94 @Override 95 public Intent getLongClickIntent() { 96 return null; 97 } 98 99 @Override 100 protected void handleLongClick() { 101 sendIntent("long-click", mOnLongClick, mOnLongClickUri); 102 } 103 104 private void sendIntent(String type, PendingIntent pi, String uri) { 105 try { 106 if (pi != null) { 107 if (pi.isActivity()) { 108 getHost().startActivityDismissingKeyguard(pi); 109 } else { 110 pi.send(); 111 } 112 } else if (uri != null) { 113 final Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME); 114 mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); 115 } 116 } catch (Throwable t) { 117 Log.w(TAG, "Error sending " + type + " intent", t); 118 } 119 } 120 121 @Override 122 public CharSequence getTileLabel() { 123 return getState().label; 124 } 125 126 @Override 127 protected void handleUpdateState(State state, Object arg) { 128 Intent intent = (Intent) arg; 129 if (intent == null) { 130 if (mLastIntent == null) { 131 return; 132 } 133 // No intent but need to refresh state, just use the last one. 134 intent = mLastIntent; 135 } 136 // Save the last one in case we need it later. 137 mLastIntent = intent; 138 state.contentDescription = intent.getStringExtra("contentDescription"); 139 state.label = intent.getStringExtra("label"); 140 state.icon = null; 141 final byte[] iconBitmap = intent.getByteArrayExtra("iconBitmap"); 142 if (iconBitmap != null) { 143 try { 144 state.icon = new BytesIcon(iconBitmap); 145 } catch (Throwable t) { 146 Log.w(TAG, "Error loading icon bitmap, length " + iconBitmap.length, t); 147 } 148 } else { 149 final int iconId = intent.getIntExtra("iconId", 0); 150 if (iconId != 0) { 151 final String iconPackage = intent.getStringExtra("iconPackage"); 152 if (!TextUtils.isEmpty(iconPackage)) { 153 state.icon = new PackageDrawableIcon(iconPackage, iconId); 154 } else { 155 state.icon = ResourceIcon.get(iconId); 156 } 157 } 158 } 159 mOnClick = intent.getParcelableExtra("onClick"); 160 mOnClickUri = intent.getStringExtra("onClickUri"); 161 mOnLongClick = intent.getParcelableExtra("onLongClick"); 162 mOnLongClickUri = intent.getStringExtra("onLongClickUri"); 163 mIntentPackage = intent.getStringExtra("package"); 164 mIntentPackage = mIntentPackage == null ? "" : mIntentPackage; 165 } 166 167 @Override 168 public int getMetricsCategory() { 169 return MetricsEvent.QS_INTENT; 170 } 171 172 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 173 @Override 174 public void onReceive(Context context, Intent intent) { 175 refreshState(intent); 176 } 177 }; 178 179 private static class BytesIcon extends Icon { 180 private final byte[] mBytes; 181 182 public BytesIcon(byte[] bytes) { 183 mBytes = bytes; 184 } 185 186 @Override 187 public Drawable getDrawable(Context context) { 188 final Bitmap b = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length); 189 return new BitmapDrawable(context.getResources(), b); 190 } 191 192 @Override 193 public boolean equals(Object o) { 194 return o instanceof BytesIcon && Arrays.equals(((BytesIcon) o).mBytes, mBytes); 195 } 196 197 @Override 198 public String toString() { 199 return String.format("BytesIcon[len=%s]", mBytes.length); 200 } 201 } 202 203 private class PackageDrawableIcon extends Icon { 204 private final String mPackage; 205 private final int mResId; 206 207 public PackageDrawableIcon(String pkg, int resId) { 208 mPackage = pkg; 209 mResId = resId; 210 } 211 212 @Override 213 public boolean equals(Object o) { 214 if (!(o instanceof PackageDrawableIcon)) return false; 215 final PackageDrawableIcon other = (PackageDrawableIcon) o; 216 return Objects.equals(other.mPackage, mPackage) && other.mResId == mResId; 217 } 218 219 @Override 220 public Drawable getDrawable(Context context) { 221 try { 222 return context.createPackageContext(mPackage, 0).getDrawable(mResId); 223 } catch (Throwable t) { 224 Log.w(TAG, "Error loading package drawable pkg=" + mPackage + " id=" + mResId, t); 225 return null; 226 } 227 } 228 229 @Override 230 public String toString() { 231 return String.format("PackageDrawableIcon[pkg=%s,id=0x%08x]", mPackage, mResId); 232 } 233 } 234} 235