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