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