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