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