1 /*
2  * Copyright (C) 2015 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.cts.widgetprovider;
18 
19 import android.app.Service;
20 import android.appwidget.AppWidgetHost;
21 import android.appwidget.AppWidgetHostView;
22 import android.appwidget.AppWidgetManager;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Messenger;
33 import android.os.RemoteException;
34 import android.os.SystemClock;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.util.Log;
38 import android.widget.RemoteViews;
39 
40 import java.util.List;
41 import java.util.concurrent.Semaphore;
42 import java.util.concurrent.TimeUnit;
43 
44 /**
45  * Service that acts as AppWidgetHost that listens to onProvidersChanged callbacks and updates the
46  * internally stored list of profile widgets. The service reacts to messages sent from the device
47  * side tests and returns whether the expected widget provider is currently present or not.
48  */
49 public class SimpleAppWidgetHostService extends Service {
50     private static final String TAG = "SimpleAppWidgetHostService";
51 
52     private static final int MSG_RESULT = 0;
53     private static final int MSG_PROVIDER_PRESENT = 1;
54     private static final int MSG_PROVIDER_UPDATES = 2;
55 
56     private static final int RESULT_UNKNOWN = 0;
57     private static final int RESULT_PRESENT = 1;
58     private static final int RESULT_NOT_PRESENT = 2;
59     private static final int RESULT_INTERRUPTED = 3;
60     private static final int RESULT_TIMEOUT = 4;
61 
62     private static final long GET_PROVIDER_TIMEOUT_MILLIS = 30 * 1000; // 30 seconds
63 
64     public static final String USER_EXTRA = "user-extra";
65     public static final String PACKAGE_EXTRA = "package-extra";
66     public static final String REPLY_EXTRA = "reply-extra";
67 
68     private AppWidgetManager mAppWidgetManager;
69     private SimpleAppWidgetHost mAppWidgetHost;
70     private SimpleAppWidgetHostView mAppWidgetHostView;
71     private int mAppWidgetId;
72     private Messenger mMessenger;
73     private UserHandle mUserHandle;
74     private Semaphore mWidgetUpdateSemaphore = new Semaphore(0);
75     private RemoteViews mRemoteViews;
76 
77     class CheckHandler extends Handler {
CheckHandler(Looper looper)78         public CheckHandler(Looper looper) {
79             super(looper);
80         }
81 
82         @Override
handleMessage(Message msg)83         public void handleMessage(Message msg) {
84             Bundle params = null;
85             if (msg.obj instanceof Bundle) {
86                 params = (Bundle) (msg.obj);
87             }
88             try {
89                 switch (msg.what) {
90                     case MSG_PROVIDER_PRESENT: {
91                         Log.d(TAG, "MSG_PROVIDER_PRESENT");
92                         int result = RESULT_UNKNOWN;
93                         try {
94                             AppWidgetProviderInfo info = mAppWidgetHost.getProvider(params);
95                             result = (info != null) ? RESULT_PRESENT : RESULT_NOT_PRESENT;
96                             if (info != null) {
97                                 bindAppWidget(info);
98                             }
99                         } catch (InterruptedException e) {
100                             result = RESULT_INTERRUPTED;
101                         }
102                         msg.replyTo.send(Message.obtain(null, MSG_RESULT, result
103                                 , 0 /* not used */));
104                         break;
105                     }
106                     case MSG_PROVIDER_UPDATES: {
107                         Log.d(TAG, "MSG_PROVIDER_UPDATES");
108                         int result = RESULT_UNKNOWN;
109                         try {
110                             updateWidgetViaWidgetId();
111                             boolean update = waitForUpdate();
112                             result = update ? RESULT_PRESENT : RESULT_NOT_PRESENT;
113                         } catch (InterruptedException e) {
114                             result = RESULT_INTERRUPTED;
115                         }
116                         msg.replyTo.send(Message.obtain(null, MSG_RESULT, result
117                                 , 0 /* not used */));
118                         break;
119                     }
120                     default:
121                         super.handleMessage(msg);
122                 }
123             } catch (RemoteException e) {
124                 Log.e(TAG, "Failed to report test status");
125             }
126         }
127     }
128 
129     @Override
onStartCommand(Intent intent, int flags, int startId)130     public int onStartCommand(Intent intent, int flags, int startId) {
131         if (intent == null) {
132             return START_NOT_STICKY;
133         }
134         if ("com.android.cts.widgetprovider.REGISTER_CALLBACK".equals(intent.getAction())) {
135             mUserHandle = getUserHandleArgument(this, USER_EXTRA, intent);
136             Log.d(TAG, "mUserHandle=" + mUserHandle);
137             setup();
138         }
139         return START_STICKY;
140     }
141 
setup()142     private void setup() {
143         HandlerThread handlerThread = new HandlerThread("Widget test callback handler");
144         handlerThread.start();
145         mMessenger = new Messenger(new CheckHandler(handlerThread.getLooper()));
146         mAppWidgetManager = (AppWidgetManager) getSystemService(Context.APPWIDGET_SERVICE);
147         mAppWidgetHost = new SimpleAppWidgetHost(this, 0);
148         mAppWidgetHost.deleteHost();
149         mAppWidgetHost.startListening();
150     }
151 
152     @Override
onBind(Intent intent)153     public IBinder onBind(Intent intent) {
154         return mMessenger.getBinder();
155     }
156 
157     @Override
onDestroy()158     public void onDestroy() {
159         mAppWidgetHost.stopListening();
160         mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
161         mAppWidgetHost.deleteHost();
162     }
163 
bindAppWidget(AppWidgetProviderInfo info)164     private void bindAppWidget(AppWidgetProviderInfo info) {
165         mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
166         Log.d(TAG, "Registering app widget with id:" + mAppWidgetId);
167         mAppWidgetManager.bindAppWidgetIdIfAllowed(mAppWidgetId, mUserHandle, info.provider, null);
168         mAppWidgetHostView = (SimpleAppWidgetHostView) mAppWidgetHost.createView(this,
169                 mAppWidgetId, info);
170         mRemoteViews = new RemoteViews(info.provider.getPackageName(), R.layout.simple_widget);
171     }
172 
getUserHandleArgument(Context context, String key, Intent intent)173     private UserHandle getUserHandleArgument(Context context, String key,
174             Intent intent) {
175         UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
176         int serial = intent.getIntExtra(key, 0);
177         Log.d(TAG, "userId=" + serial);
178         return um.getUserForSerialNumber(serial);
179     }
180 
updateWidgetViaWidgetId()181     private void updateWidgetViaWidgetId() {
182         Log.d(TAG, "Forcing widget update via widget id");
183         mWidgetUpdateSemaphore.drainPermits();
184         // trigger a widget update
185         mAppWidgetManager.updateAppWidget(mAppWidgetId, mRemoteViews);
186     }
187 
waitForUpdate()188     private boolean waitForUpdate() throws InterruptedException {
189         // wait for updateAppWidget to arrive
190         return mWidgetUpdateSemaphore.tryAcquire(20, TimeUnit.SECONDS);
191     }
192 
193     private class SimpleAppWidgetHost extends AppWidgetHost {
194         private List<AppWidgetProviderInfo> mProviders;
195         private Semaphore mSemaphore = new Semaphore(0);
SimpleAppWidgetHost(Context context, int hostId)196         public SimpleAppWidgetHost(Context context, int hostId) {
197             super(context, hostId);
198             synchronized (this) {
199                 mProviders = mAppWidgetManager.getInstalledProvidersForProfile(mUserHandle);
200             }
201         }
202 
203         @Override
onProvidersChanged()204         protected void onProvidersChanged() {
205             super.onProvidersChanged();
206             Log.d(TAG, "onProvidersChanged callback received");
207             synchronized (this) {
208                 mProviders = mAppWidgetManager.getInstalledProvidersForProfile(mUserHandle);
209             }
210             mSemaphore.release();
211         }
212 
213         @Override
onCreateView(Context context, int id, AppWidgetProviderInfo info)214         protected AppWidgetHostView onCreateView(Context context, int id, AppWidgetProviderInfo info) {
215             return new SimpleAppWidgetHostView(context);
216         }
217 
getProvider(Bundle params)218         public AppWidgetProviderInfo getProvider(Bundle params) throws InterruptedException {
219             final long startTime = SystemClock.elapsedRealtime();
220             long nextTimeout = GET_PROVIDER_TIMEOUT_MILLIS;
221             String packageName = params.getString(PACKAGE_EXTRA);
222             while ((nextTimeout > 0) && mSemaphore.tryAcquire(nextTimeout, TimeUnit.MILLISECONDS)) {
223                 mSemaphore.drainPermits();
224                 Log.d(TAG, "checking for " + packageName + " " + mUserHandle);
225                 synchronized (this) {
226                     for (AppWidgetProviderInfo providerInfo : mProviders) {
227                         if (providerInfo.provider.getPackageName().equals(packageName)) {
228                             Log.d(TAG, "Provider exists " + packageName
229                                     + " for user " + mUserHandle);
230                             return providerInfo;
231                         }
232                     }
233                     nextTimeout = startTime + GET_PROVIDER_TIMEOUT_MILLIS
234                             - SystemClock.elapsedRealtime();
235                 }
236             }
237             return null;
238         }
239     }
240 
241     private class SimpleAppWidgetHostView extends AppWidgetHostView {
SimpleAppWidgetHostView(Context context)242         public SimpleAppWidgetHostView(Context context) {
243             super(context);
244         }
245 
246         @Override
updateAppWidget(RemoteViews views)247         public void updateAppWidget(RemoteViews views) {
248             super.updateAppWidget(views);
249             Log.d(TAG, "Host view received widget update");
250             mWidgetUpdateSemaphore.release();
251         }
252     }
253 }
254