AppWidgetService.java revision 267f4d6a22aade4f63831e166312647212816a3d
1/* 2 * Copyright (C) 2007 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.server; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.appwidget.AppWidgetManager; 22import android.appwidget.AppWidgetProviderInfo; 23import android.content.BroadcastReceiver; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.ActivityInfo; 29import android.content.pm.ApplicationInfo; 30import android.content.pm.PackageManager; 31import android.content.pm.PackageInfo; 32import android.content.pm.ResolveInfo; 33import android.content.res.Resources; 34import android.content.res.TypedArray; 35import android.content.res.XmlResourceParser; 36import android.net.Uri; 37import android.os.Binder; 38import android.os.Bundle; 39import android.os.Process; 40import android.os.RemoteException; 41import android.os.SystemClock; 42import android.util.AttributeSet; 43import android.util.Slog; 44import android.util.TypedValue; 45import android.util.Xml; 46import android.widget.RemoteViews; 47 48import java.io.IOException; 49import java.io.File; 50import java.io.FileDescriptor; 51import java.io.FileInputStream; 52import java.io.FileOutputStream; 53import java.io.PrintWriter; 54import java.util.ArrayList; 55import java.util.List; 56import java.util.Locale; 57import java.util.HashMap; 58import java.util.HashSet; 59 60import com.android.internal.appwidget.IAppWidgetService; 61import com.android.internal.appwidget.IAppWidgetHost; 62import com.android.internal.util.FastXmlSerializer; 63 64import org.xmlpull.v1.XmlPullParser; 65import org.xmlpull.v1.XmlPullParserException; 66import org.xmlpull.v1.XmlSerializer; 67 68class AppWidgetService extends IAppWidgetService.Stub 69{ 70 private static final String TAG = "AppWidgetService"; 71 72 private static final String SETTINGS_FILENAME = "appwidgets.xml"; 73 private static final String SETTINGS_TMP_FILENAME = SETTINGS_FILENAME + ".tmp"; 74 private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes 75 76 /* 77 * When identifying a Host or Provider based on the calling process, use the uid field. 78 * When identifying a Host or Provider based on a package manager broadcast, use the 79 * package given. 80 */ 81 82 static class Provider { 83 int uid; 84 AppWidgetProviderInfo info; 85 ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); 86 PendingIntent broadcast; 87 boolean zombie; // if we're in safe mode, don't prune this just because nobody references it 88 89 int tag; // for use while saving state (the index) 90 } 91 92 static class Host { 93 int uid; 94 int hostId; 95 String packageName; 96 ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); 97 IAppWidgetHost callbacks; 98 boolean zombie; // if we're in safe mode, don't prune this just because nobody references it 99 100 int tag; // for use while saving state (the index) 101 } 102 103 static class AppWidgetId { 104 int appWidgetId; 105 Provider provider; 106 RemoteViews views; 107 Host host; 108 } 109 110 Context mContext; 111 Locale mLocale; 112 PackageManager mPackageManager; 113 AlarmManager mAlarmManager; 114 ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>(); 115 int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; 116 final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); 117 ArrayList<Host> mHosts = new ArrayList<Host>(); 118 boolean mSafeMode; 119 120 AppWidgetService(Context context) { 121 mContext = context; 122 mPackageManager = context.getPackageManager(); 123 mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 124 } 125 126 public void systemReady(boolean safeMode) { 127 mSafeMode = safeMode; 128 129 loadAppWidgetList(); 130 loadStateLocked(); 131 132 // Register for the boot completed broadcast, so we can send the 133 // ENABLE broacasts. If we try to send them now, they time out, 134 // because the system isn't ready to handle them yet. 135 mContext.registerReceiver(mBroadcastReceiver, 136 new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); 137 138 // Register for configuration changes so we can update the names 139 // of the widgets when the locale changes. 140 mContext.registerReceiver(mBroadcastReceiver, 141 new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); 142 143 // Register for broadcasts about package install, etc., so we can 144 // update the provider list. 145 IntentFilter filter = new IntentFilter(); 146 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 147 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 148 filter.addDataScheme("package"); 149 mContext.registerReceiver(mBroadcastReceiver, filter); 150 // Register for events related to sdcard installation. 151 IntentFilter sdFilter = new IntentFilter(); 152 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 153 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 154 mContext.registerReceiver(mBroadcastReceiver, sdFilter); 155 } 156 157 @Override 158 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 159 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 160 != PackageManager.PERMISSION_GRANTED) { 161 pw.println("Permission Denial: can't dump from from pid=" 162 + Binder.getCallingPid() 163 + ", uid=" + Binder.getCallingUid()); 164 return; 165 } 166 167 synchronized (mAppWidgetIds) { 168 int N = mInstalledProviders.size(); 169 pw.println("Providers:"); 170 for (int i=0; i<N; i++) { 171 Provider p = mInstalledProviders.get(i); 172 AppWidgetProviderInfo info = p.info; 173 pw.print(" ["); pw.print(i); pw.print("] provider "); 174 pw.print(info.provider.flattenToShortString()); 175 pw.println(':'); 176 pw.print(" min=("); pw.print(info.minWidth); 177 pw.print("x"); pw.print(info.minHeight); 178 pw.print(") updatePeriodMillis="); 179 pw.print(info.updatePeriodMillis); 180 pw.print(" initialLayout=#"); 181 pw.print(Integer.toHexString(info.initialLayout)); 182 pw.print(" zombie="); pw.println(p.zombie); 183 } 184 185 N = mAppWidgetIds.size(); 186 pw.println(" "); 187 pw.println("AppWidgetIds:"); 188 for (int i=0; i<N; i++) { 189 AppWidgetId id = mAppWidgetIds.get(i); 190 pw.print(" ["); pw.print(i); pw.print("] id="); 191 pw.println(id.appWidgetId); 192 pw.print(" hostId="); 193 pw.print(id.host.hostId); pw.print(' '); 194 pw.print(id.host.packageName); pw.print('/'); 195 pw.println(id.host.uid); 196 if (id.provider != null) { 197 pw.print(" provider="); 198 pw.println(id.provider.info.provider.flattenToShortString()); 199 } 200 if (id.host != null) { 201 pw.print(" host.callbacks="); pw.println(id.host.callbacks); 202 } 203 if (id.views != null) { 204 pw.print(" views="); pw.println(id.views); 205 } 206 } 207 208 N = mHosts.size(); 209 pw.println(" "); 210 pw.println("Hosts:"); 211 for (int i=0; i<N; i++) { 212 Host host = mHosts.get(i); 213 pw.print(" ["); pw.print(i); pw.print("] hostId="); 214 pw.print(host.hostId); pw.print(' '); 215 pw.print(host.packageName); pw.print('/'); 216 pw.print(host.uid); pw.println(':'); 217 pw.print(" callbacks="); pw.println(host.callbacks); 218 pw.print(" instances.size="); pw.print(host.instances.size()); 219 pw.print(" zombie="); pw.println(host.zombie); 220 } 221 } 222 } 223 224 public int allocateAppWidgetId(String packageName, int hostId) { 225 int callingUid = enforceCallingUid(packageName); 226 synchronized (mAppWidgetIds) { 227 int appWidgetId = mNextAppWidgetId++; 228 229 Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); 230 231 AppWidgetId id = new AppWidgetId(); 232 id.appWidgetId = appWidgetId; 233 id.host = host; 234 235 host.instances.add(id); 236 mAppWidgetIds.add(id); 237 238 saveStateLocked(); 239 240 return appWidgetId; 241 } 242 } 243 244 public void deleteAppWidgetId(int appWidgetId) { 245 synchronized (mAppWidgetIds) { 246 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); 247 if (id != null) { 248 deleteAppWidgetLocked(id); 249 saveStateLocked(); 250 } 251 } 252 } 253 254 public void deleteHost(int hostId) { 255 synchronized (mAppWidgetIds) { 256 int callingUid = getCallingUid(); 257 Host host = lookupHostLocked(callingUid, hostId); 258 if (host != null) { 259 deleteHostLocked(host); 260 saveStateLocked(); 261 } 262 } 263 } 264 265 public void deleteAllHosts() { 266 synchronized (mAppWidgetIds) { 267 int callingUid = getCallingUid(); 268 final int N = mHosts.size(); 269 boolean changed = false; 270 for (int i=N-1; i>=0; i--) { 271 Host host = mHosts.get(i); 272 if (host.uid == callingUid) { 273 deleteHostLocked(host); 274 changed = true; 275 } 276 } 277 if (changed) { 278 saveStateLocked(); 279 } 280 } 281 } 282 283 void deleteHostLocked(Host host) { 284 final int N = host.instances.size(); 285 for (int i=N-1; i>=0; i--) { 286 AppWidgetId id = host.instances.get(i); 287 deleteAppWidgetLocked(id); 288 } 289 host.instances.clear(); 290 mHosts.remove(host); 291 // it's gone or going away, abruptly drop the callback connection 292 host.callbacks = null; 293 } 294 295 void deleteAppWidgetLocked(AppWidgetId id) { 296 Host host = id.host; 297 host.instances.remove(id); 298 pruneHostLocked(host); 299 300 mAppWidgetIds.remove(id); 301 302 Provider p = id.provider; 303 if (p != null) { 304 p.instances.remove(id); 305 if (!p.zombie) { 306 // send the broacast saying that this appWidgetId has been deleted 307 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); 308 intent.setComponent(p.info.provider); 309 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); 310 mContext.sendBroadcast(intent); 311 if (p.instances.size() == 0) { 312 // cancel the future updates 313 cancelBroadcasts(p); 314 315 // send the broacast saying that the provider is not in use any more 316 intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); 317 intent.setComponent(p.info.provider); 318 mContext.sendBroadcast(intent); 319 } 320 } 321 } 322 } 323 324 void cancelBroadcasts(Provider p) { 325 if (p.broadcast != null) { 326 mAlarmManager.cancel(p.broadcast); 327 long token = Binder.clearCallingIdentity(); 328 try { 329 p.broadcast.cancel(); 330 } finally { 331 Binder.restoreCallingIdentity(token); 332 } 333 p.broadcast = null; 334 } 335 } 336 337 public void bindAppWidgetId(int appWidgetId, ComponentName provider) { 338 mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, 339 "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); 340 synchronized (mAppWidgetIds) { 341 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); 342 if (id == null) { 343 throw new IllegalArgumentException("bad appWidgetId"); 344 } 345 if (id.provider != null) { 346 throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to " 347 + id.provider.info.provider); 348 } 349 Provider p = lookupProviderLocked(provider); 350 if (p == null) { 351 throw new IllegalArgumentException("not a appwidget provider: " + provider); 352 } 353 if (p.zombie) { 354 throw new IllegalArgumentException("can't bind to a 3rd party provider in" 355 + " safe mode: " + provider); 356 } 357 358 id.provider = p; 359 p.instances.add(id); 360 int instancesSize = p.instances.size(); 361 if (instancesSize == 1) { 362 // tell the provider that it's ready 363 sendEnableIntentLocked(p); 364 } 365 366 // send an update now -- We need this update now, and just for this appWidgetId. 367 // It's less critical when the next one happens, so when we schdule the next one, 368 // we add updatePeriodMillis to its start time. That time will have some slop, 369 // but that's okay. 370 sendUpdateIntentLocked(p, new int[] { appWidgetId }); 371 372 // schedule the future updates 373 registerForBroadcastsLocked(p, getAppWidgetIds(p)); 374 saveStateLocked(); 375 } 376 } 377 378 public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { 379 synchronized (mAppWidgetIds) { 380 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); 381 if (id != null && id.provider != null && !id.provider.zombie) { 382 return id.provider.info; 383 } 384 return null; 385 } 386 } 387 388 public RemoteViews getAppWidgetViews(int appWidgetId) { 389 synchronized (mAppWidgetIds) { 390 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); 391 if (id != null) { 392 return id.views; 393 } 394 return null; 395 } 396 } 397 398 public List<AppWidgetProviderInfo> getInstalledProviders() { 399 synchronized (mAppWidgetIds) { 400 final int N = mInstalledProviders.size(); 401 ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); 402 for (int i=0; i<N; i++) { 403 Provider p = mInstalledProviders.get(i); 404 if (!p.zombie) { 405 result.add(p.info); 406 } 407 } 408 return result; 409 } 410 } 411 412 public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { 413 if (appWidgetIds == null) { 414 return; 415 } 416 if (appWidgetIds.length == 0) { 417 return; 418 } 419 final int N = appWidgetIds.length; 420 421 synchronized (mAppWidgetIds) { 422 for (int i=0; i<N; i++) { 423 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); 424 updateAppWidgetInstanceLocked(id, views); 425 } 426 } 427 } 428 429 public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { 430 if (appWidgetIds == null) { 431 return; 432 } 433 if (appWidgetIds.length == 0) { 434 return; 435 } 436 final int N = appWidgetIds.length; 437 438 synchronized (mAppWidgetIds) { 439 for (int i=0; i<N; i++) { 440 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); 441 updateAppWidgetInstanceLocked(id, views, true); 442 } 443 } 444 } 445 446 public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { 447 if (appWidgetIds == null) { 448 return; 449 } 450 if (appWidgetIds.length == 0) { 451 return; 452 } 453 final int N = appWidgetIds.length; 454 455 synchronized (mAppWidgetIds) { 456 for (int i=0; i<N; i++) { 457 AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); 458 notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); 459 } 460 } 461 } 462 463 public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { 464 synchronized (mAppWidgetIds) { 465 Provider p = lookupProviderLocked(provider); 466 if (p == null) { 467 Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); 468 return; 469 } 470 ArrayList<AppWidgetId> instances = p.instances; 471 final int N = instances.size(); 472 for (int i=0; i<N; i++) { 473 AppWidgetId id = instances.get(i); 474 updateAppWidgetInstanceLocked(id, views); 475 } 476 } 477 } 478 479 void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { 480 updateAppWidgetInstanceLocked(id, views, false); 481 } 482 483 void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { 484 // allow for stale appWidgetIds and other badness 485 // lookup also checks that the calling process can access the appWidgetId 486 // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) 487 if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { 488 489 // We do not want to save this RemoteViews 490 if (!isPartialUpdate) id.views = views; 491 492 // is anyone listening? 493 if (id.host.callbacks != null) { 494 try { 495 // the lock is held, but this is a oneway call 496 id.host.callbacks.updateAppWidget(id.appWidgetId, views); 497 } catch (RemoteException e) { 498 // It failed; remove the callback. No need to prune because 499 // we know that this host is still referenced by this instance. 500 id.host.callbacks = null; 501 } 502 } 503 } 504 } 505 506 void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { 507 // allow for stale appWidgetIds and other badness 508 // lookup also checks that the calling process can access the appWidgetId 509 // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) 510 if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { 511 // is anyone listening? 512 if (id.host.callbacks != null) { 513 try { 514 // the lock is held, but this is a oneway call 515 id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); 516 } catch (RemoteException e) { 517 // It failed; remove the callback. No need to prune because 518 // we know that this host is still referenced by this instance. 519 id.host.callbacks = null; 520 } 521 } 522 } 523 } 524 525 public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, 526 List<RemoteViews> updatedViews) { 527 int callingUid = enforceCallingUid(packageName); 528 synchronized (mAppWidgetIds) { 529 Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); 530 host.callbacks = callbacks; 531 532 updatedViews.clear(); 533 534 ArrayList<AppWidgetId> instances = host.instances; 535 int N = instances.size(); 536 int[] updatedIds = new int[N]; 537 for (int i=0; i<N; i++) { 538 AppWidgetId id = instances.get(i); 539 updatedIds[i] = id.appWidgetId; 540 updatedViews.add(id.views); 541 } 542 return updatedIds; 543 } 544 } 545 546 public void stopListening(int hostId) { 547 synchronized (mAppWidgetIds) { 548 Host host = lookupHostLocked(getCallingUid(), hostId); 549 if (host != null) { 550 host.callbacks = null; 551 pruneHostLocked(host); 552 } 553 } 554 } 555 556 boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { 557 if (id.host.uid == callingUid) { 558 // Apps hosting the AppWidget have access to it. 559 return true; 560 } 561 if (id.provider != null && id.provider.uid == callingUid) { 562 // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) 563 return true; 564 } 565 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) 566 == PackageManager.PERMISSION_GRANTED) { 567 // Apps that can bind have access to all appWidgetIds. 568 return true; 569 } 570 // Nobody else can access it. 571 return false; 572 } 573 574 AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { 575 int callingUid = getCallingUid(); 576 final int N = mAppWidgetIds.size(); 577 for (int i=0; i<N; i++) { 578 AppWidgetId id = mAppWidgetIds.get(i); 579 if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { 580 return id; 581 } 582 } 583 return null; 584 } 585 586 Provider lookupProviderLocked(ComponentName provider) { 587 final String className = provider.getClassName(); 588 final int N = mInstalledProviders.size(); 589 for (int i=0; i<N; i++) { 590 Provider p = mInstalledProviders.get(i); 591 if (p.info.provider.equals(provider) || className.equals(p.info.oldName)) { 592 return p; 593 } 594 } 595 return null; 596 } 597 598 Host lookupHostLocked(int uid, int hostId) { 599 final int N = mHosts.size(); 600 for (int i=0; i<N; i++) { 601 Host h = mHosts.get(i); 602 if (h.uid == uid && h.hostId == hostId) { 603 return h; 604 } 605 } 606 return null; 607 } 608 609 Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { 610 final int N = mHosts.size(); 611 for (int i=0; i<N; i++) { 612 Host h = mHosts.get(i); 613 if (h.hostId == hostId && h.packageName.equals(packageName)) { 614 return h; 615 } 616 } 617 Host host = new Host(); 618 host.packageName = packageName; 619 host.uid = uid; 620 host.hostId = hostId; 621 mHosts.add(host); 622 return host; 623 } 624 625 void pruneHostLocked(Host host) { 626 if (host.instances.size() == 0 && host.callbacks == null) { 627 mHosts.remove(host); 628 } 629 } 630 631 void loadAppWidgetList() { 632 PackageManager pm = mPackageManager; 633 634 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 635 List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, 636 PackageManager.GET_META_DATA); 637 638 final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 639 for (int i=0; i<N; i++) { 640 ResolveInfo ri = broadcastReceivers.get(i); 641 addProviderLocked(ri); 642 } 643 } 644 645 boolean addProviderLocked(ResolveInfo ri) { 646 Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, 647 ri.activityInfo.name), ri); 648 if (p != null) { 649 mInstalledProviders.add(p); 650 return true; 651 } else { 652 return false; 653 } 654 } 655 656 void removeProviderLocked(int index, Provider p) { 657 int N = p.instances.size(); 658 for (int i=0; i<N; i++) { 659 AppWidgetId id = p.instances.get(i); 660 // Call back with empty RemoteViews 661 updateAppWidgetInstanceLocked(id, null); 662 // Stop telling the host about updates for this from now on 663 cancelBroadcasts(p); 664 // clear out references to this appWidgetId 665 id.host.instances.remove(id); 666 mAppWidgetIds.remove(id); 667 id.provider = null; 668 pruneHostLocked(id.host); 669 id.host = null; 670 } 671 p.instances.clear(); 672 mInstalledProviders.remove(index); 673 // no need to send the DISABLE broadcast, since the receiver is gone anyway 674 cancelBroadcasts(p); 675 } 676 677 void sendEnableIntentLocked(Provider p) { 678 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); 679 intent.setComponent(p.info.provider); 680 mContext.sendBroadcast(intent); 681 } 682 683 void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { 684 if (appWidgetIds != null && appWidgetIds.length > 0) { 685 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 686 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); 687 intent.setComponent(p.info.provider); 688 mContext.sendBroadcast(intent); 689 } 690 } 691 692 void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { 693 if (p.info.updatePeriodMillis > 0) { 694 // if this is the first instance, set the alarm. otherwise, 695 // rely on the fact that we've already set it and that 696 // PendingIntent.getBroadcast will update the extras. 697 boolean alreadyRegistered = p.broadcast != null; 698 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 699 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); 700 intent.setComponent(p.info.provider); 701 long token = Binder.clearCallingIdentity(); 702 try { 703 p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, 704 PendingIntent.FLAG_UPDATE_CURRENT); 705 } finally { 706 Binder.restoreCallingIdentity(token); 707 } 708 if (!alreadyRegistered) { 709 long period = p.info.updatePeriodMillis; 710 if (period < MIN_UPDATE_PERIOD) { 711 period = MIN_UPDATE_PERIOD; 712 } 713 mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 714 SystemClock.elapsedRealtime() + period, period, p.broadcast); 715 } 716 } 717 } 718 719 static int[] getAppWidgetIds(Provider p) { 720 int instancesSize = p.instances.size(); 721 int appWidgetIds[] = new int[instancesSize]; 722 for (int i=0; i<instancesSize; i++) { 723 appWidgetIds[i] = p.instances.get(i).appWidgetId; 724 } 725 return appWidgetIds; 726 } 727 728 public int[] getAppWidgetIds(ComponentName provider) { 729 synchronized (mAppWidgetIds) { 730 Provider p = lookupProviderLocked(provider); 731 if (p != null && getCallingUid() == p.uid) { 732 return getAppWidgetIds(p); 733 } else { 734 return new int[0]; 735 } 736 } 737 } 738 739 private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { 740 Provider p = null; 741 742 ActivityInfo activityInfo = ri.activityInfo; 743 XmlResourceParser parser = null; 744 try { 745 parser = activityInfo.loadXmlMetaData(mPackageManager, 746 AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); 747 if (parser == null) { 748 Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + " meta-data for " 749 + "AppWidget provider '" + component + '\''); 750 return null; 751 } 752 753 AttributeSet attrs = Xml.asAttributeSet(parser); 754 755 int type; 756 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 757 && type != XmlPullParser.START_TAG) { 758 // drain whitespace, comments, etc. 759 } 760 761 String nodeName = parser.getName(); 762 if (!"appwidget-provider".equals(nodeName)) { 763 Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" 764 + " AppWidget provider '" + component + '\''); 765 return null; 766 } 767 768 p = new Provider(); 769 AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); 770 // If metaData was null, we would have returned earlier when getting 771 // the parser No need to do the check here 772 info.oldName = activityInfo.metaData.getString( 773 AppWidgetManager.META_DATA_APPWIDGET_OLD_NAME); 774 775 info.provider = component; 776 p.uid = activityInfo.applicationInfo.uid; 777 778 Resources res = mPackageManager.getResourcesForApplication( 779 activityInfo.applicationInfo); 780 781 TypedArray sa = res.obtainAttributes(attrs, 782 com.android.internal.R.styleable.AppWidgetProviderInfo); 783 784 // These dimensions has to be resolved in the application's context. 785 // We simply send back the raw complex data, which will be 786 // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. 787 TypedValue value = sa.peekValue( 788 com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); 789 info.minWidth = value != null ? value.data : 0; 790 value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); 791 info.minHeight = value != null ? value.data : 0; 792 793 info.updatePeriodMillis = sa.getInt( 794 com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); 795 info.initialLayout = sa.getResourceId( 796 com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); 797 String className = sa.getString( 798 com.android.internal.R.styleable.AppWidgetProviderInfo_configure); 799 if (className != null) { 800 info.configure = new ComponentName(component.getPackageName(), className); 801 } 802 info.label = activityInfo.loadLabel(mPackageManager).toString(); 803 info.icon = ri.getIconResource(); 804 info.previewImage = sa.getResourceId( 805 com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); 806 807 sa.recycle(); 808 } catch (Exception e) { 809 // Ok to catch Exception here, because anything going wrong because 810 // of what a client process passes to us should not be fatal for the 811 // system process. 812 Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); 813 return null; 814 } finally { 815 if (parser != null) parser.close(); 816 } 817 return p; 818 } 819 820 int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { 821 PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); 822 if (pkgInfo == null || pkgInfo.applicationInfo == null) { 823 throw new PackageManager.NameNotFoundException(); 824 } 825 return pkgInfo.applicationInfo.uid; 826 } 827 828 int enforceCallingUid(String packageName) throws IllegalArgumentException { 829 int callingUid = getCallingUid(); 830 int packageUid; 831 try { 832 packageUid = getUidForPackage(packageName); 833 } catch (PackageManager.NameNotFoundException ex) { 834 throw new IllegalArgumentException("packageName and uid don't match packageName=" 835 + packageName); 836 } 837 if (callingUid != packageUid && Process.supportsProcesses()) { 838 throw new IllegalArgumentException("packageName and uid don't match packageName=" 839 + packageName); 840 } 841 return callingUid; 842 } 843 844 void sendInitialBroadcasts() { 845 synchronized (mAppWidgetIds) { 846 final int N = mInstalledProviders.size(); 847 for (int i=0; i<N; i++) { 848 Provider p = mInstalledProviders.get(i); 849 if (p.instances.size() > 0) { 850 sendEnableIntentLocked(p); 851 int[] appWidgetIds = getAppWidgetIds(p); 852 sendUpdateIntentLocked(p, appWidgetIds); 853 registerForBroadcastsLocked(p, appWidgetIds); 854 } 855 } 856 } 857 } 858 859 // only call from initialization -- it assumes that the data structures are all empty 860 void loadStateLocked() { 861 File temp = savedStateTempFile(); 862 File real = savedStateRealFile(); 863 864 // prefer the real file. If it doesn't exist, use the temp one, and then copy it to the 865 // real one. if there is both a real file and a temp one, assume that the temp one isn't 866 // fully written and delete it. 867 if (real.exists()) { 868 readStateFromFileLocked(real); 869 if (temp.exists()) { 870 //noinspection ResultOfMethodCallIgnored 871 temp.delete(); 872 } 873 } else if (temp.exists()) { 874 readStateFromFileLocked(temp); 875 //noinspection ResultOfMethodCallIgnored 876 temp.renameTo(real); 877 } 878 } 879 880 void saveStateLocked() { 881 File temp = savedStateTempFile(); 882 File real = savedStateRealFile(); 883 884 if (!real.exists()) { 885 // If the real one doesn't exist, it's either because this is the first time 886 // or because something went wrong while copying them. In this case, we can't 887 // trust anything that's in temp. In order to have the loadState code not 888 // use the temporary one until it's fully written, create an empty file 889 // for real, which will we'll shortly delete. 890 try { 891 //noinspection ResultOfMethodCallIgnored 892 real.createNewFile(); 893 } catch (IOException e) { 894 // Ignore 895 } 896 } 897 898 if (temp.exists()) { 899 //noinspection ResultOfMethodCallIgnored 900 temp.delete(); 901 } 902 903 if (!writeStateToFileLocked(temp)) { 904 Slog.w(TAG, "Failed to persist new settings"); 905 return; 906 } 907 908 //noinspection ResultOfMethodCallIgnored 909 real.delete(); 910 //noinspection ResultOfMethodCallIgnored 911 temp.renameTo(real); 912 } 913 914 boolean writeStateToFileLocked(File file) { 915 FileOutputStream stream = null; 916 int N; 917 918 try { 919 stream = new FileOutputStream(file, false); 920 XmlSerializer out = new FastXmlSerializer(); 921 out.setOutput(stream, "utf-8"); 922 out.startDocument(null, true); 923 924 925 out.startTag(null, "gs"); 926 927 int providerIndex = 0; 928 N = mInstalledProviders.size(); 929 for (int i=0; i<N; i++) { 930 Provider p = mInstalledProviders.get(i); 931 if (p.instances.size() > 0) { 932 out.startTag(null, "p"); 933 out.attribute(null, "pkg", p.info.provider.getPackageName()); 934 out.attribute(null, "cl", p.info.provider.getClassName()); 935 out.endTag(null, "p"); 936 p.tag = providerIndex; 937 providerIndex++; 938 } 939 } 940 941 N = mHosts.size(); 942 for (int i=0; i<N; i++) { 943 Host host = mHosts.get(i); 944 out.startTag(null, "h"); 945 out.attribute(null, "pkg", host.packageName); 946 out.attribute(null, "id", Integer.toHexString(host.hostId)); 947 out.endTag(null, "h"); 948 host.tag = i; 949 } 950 951 N = mAppWidgetIds.size(); 952 for (int i=0; i<N; i++) { 953 AppWidgetId id = mAppWidgetIds.get(i); 954 out.startTag(null, "g"); 955 out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); 956 out.attribute(null, "h", Integer.toHexString(id.host.tag)); 957 if (id.provider != null) { 958 out.attribute(null, "p", Integer.toHexString(id.provider.tag)); 959 } 960 out.endTag(null, "g"); 961 } 962 963 out.endTag(null, "gs"); 964 965 out.endDocument(); 966 stream.close(); 967 return true; 968 } catch (IOException e) { 969 try { 970 if (stream != null) { 971 stream.close(); 972 } 973 } catch (IOException ex) { 974 // Ignore 975 } 976 if (file.exists()) { 977 //noinspection ResultOfMethodCallIgnored 978 file.delete(); 979 } 980 return false; 981 } 982 } 983 984 void readStateFromFileLocked(File file) { 985 FileInputStream stream = null; 986 987 boolean success = false; 988 989 try { 990 stream = new FileInputStream(file); 991 XmlPullParser parser = Xml.newPullParser(); 992 parser.setInput(stream, null); 993 994 int type; 995 int providerIndex = 0; 996 HashMap<Integer,Provider> loadedProviders = new HashMap<Integer, Provider>(); 997 do { 998 type = parser.next(); 999 if (type == XmlPullParser.START_TAG) { 1000 String tag = parser.getName(); 1001 if ("p".equals(tag)) { 1002 // TODO: do we need to check that this package has the same signature 1003 // as before? 1004 String pkg = parser.getAttributeValue(null, "pkg"); 1005 String cl = parser.getAttributeValue(null, "cl"); 1006 1007 final PackageManager packageManager = mContext.getPackageManager(); 1008 try { 1009 packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); 1010 } catch (PackageManager.NameNotFoundException e) { 1011 String[] pkgs = packageManager.currentToCanonicalPackageNames( 1012 new String[] { pkg }); 1013 pkg = pkgs[0]; 1014 } 1015 1016 Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); 1017 if (p == null && mSafeMode) { 1018 // if we're in safe mode, make a temporary one 1019 p = new Provider(); 1020 p.info = new AppWidgetProviderInfo(); 1021 p.info.provider = new ComponentName(pkg, cl); 1022 p.zombie = true; 1023 mInstalledProviders.add(p); 1024 } 1025 if (p != null) { 1026 // if it wasn't uninstalled or something 1027 loadedProviders.put(providerIndex, p); 1028 } 1029 providerIndex++; 1030 } 1031 else if ("h".equals(tag)) { 1032 Host host = new Host(); 1033 1034 // TODO: do we need to check that this package has the same signature 1035 // as before? 1036 host.packageName = parser.getAttributeValue(null, "pkg"); 1037 try { 1038 host.uid = getUidForPackage(host.packageName); 1039 } catch (PackageManager.NameNotFoundException ex) { 1040 host.zombie = true; 1041 } 1042 if (!host.zombie || mSafeMode) { 1043 // In safe mode, we don't discard the hosts we don't recognize 1044 // so that they're not pruned from our list. Otherwise, we do. 1045 host.hostId = Integer.parseInt( 1046 parser.getAttributeValue(null, "id"), 16); 1047 mHosts.add(host); 1048 } 1049 } 1050 else if ("g".equals(tag)) { 1051 AppWidgetId id = new AppWidgetId(); 1052 id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); 1053 if (id.appWidgetId >= mNextAppWidgetId) { 1054 mNextAppWidgetId = id.appWidgetId + 1; 1055 } 1056 1057 String providerString = parser.getAttributeValue(null, "p"); 1058 if (providerString != null) { 1059 // there's no provider if it hasn't been bound yet. 1060 // maybe we don't have to save this, but it brings the system 1061 // to the state it was in. 1062 int pIndex = Integer.parseInt(providerString, 16); 1063 id.provider = loadedProviders.get(pIndex); 1064 if (false) { 1065 Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " 1066 + pIndex + " which is " + id.provider); 1067 } 1068 if (id.provider == null) { 1069 // This provider is gone. We just let the host figure out 1070 // that this happened when it fails to load it. 1071 continue; 1072 } 1073 } 1074 1075 int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); 1076 id.host = mHosts.get(hIndex); 1077 if (id.host == null) { 1078 // This host is gone. 1079 continue; 1080 } 1081 1082 if (id.provider != null) { 1083 id.provider.instances.add(id); 1084 } 1085 id.host.instances.add(id); 1086 mAppWidgetIds.add(id); 1087 } 1088 } 1089 } while (type != XmlPullParser.END_DOCUMENT); 1090 success = true; 1091 } catch (NullPointerException e) { 1092 Slog.w(TAG, "failed parsing " + file, e); 1093 } catch (NumberFormatException e) { 1094 Slog.w(TAG, "failed parsing " + file, e); 1095 } catch (XmlPullParserException e) { 1096 Slog.w(TAG, "failed parsing " + file, e); 1097 } catch (IOException e) { 1098 Slog.w(TAG, "failed parsing " + file, e); 1099 } catch (IndexOutOfBoundsException e) { 1100 Slog.w(TAG, "failed parsing " + file, e); 1101 } 1102 try { 1103 if (stream != null) { 1104 stream.close(); 1105 } 1106 } catch (IOException e) { 1107 // Ignore 1108 } 1109 1110 if (success) { 1111 // delete any hosts that didn't manage to get connected (should happen) 1112 // if it matters, they'll be reconnected. 1113 for (int i=mHosts.size()-1; i>=0; i--) { 1114 pruneHostLocked(mHosts.get(i)); 1115 } 1116 } else { 1117 // failed reading, clean up 1118 mAppWidgetIds.clear(); 1119 mHosts.clear(); 1120 final int N = mInstalledProviders.size(); 1121 for (int i=0; i<N; i++) { 1122 mInstalledProviders.get(i).instances.clear(); 1123 } 1124 } 1125 } 1126 1127 File savedStateTempFile() { 1128 return new File("/data/system/" + SETTINGS_TMP_FILENAME); 1129 //return new File(mContext.getFilesDir(), SETTINGS_FILENAME); 1130 } 1131 1132 File savedStateRealFile() { 1133 return new File("/data/system/" + SETTINGS_FILENAME); 1134 //return new File(mContext.getFilesDir(), SETTINGS_TMP_FILENAME); 1135 } 1136 1137 BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1138 public void onReceive(Context context, Intent intent) { 1139 String action = intent.getAction(); 1140 //Slog.d(TAG, "received " + action); 1141 if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { 1142 sendInitialBroadcasts(); 1143 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 1144 Locale revised = Locale.getDefault(); 1145 if (revised == null || mLocale == null || 1146 !(revised.equals(mLocale))) { 1147 mLocale = revised; 1148 1149 synchronized (mAppWidgetIds) { 1150 int N = mInstalledProviders.size(); 1151 for (int i=N-1; i>=0; i--) { 1152 Provider p = mInstalledProviders.get(i); 1153 String pkgName = p.info.provider.getPackageName(); 1154 updateProvidersForPackageLocked(pkgName); 1155 } 1156 saveStateLocked(); 1157 } 1158 } 1159 } else { 1160 boolean added = false; 1161 String pkgList[] = null; 1162 if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { 1163 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 1164 added = true; 1165 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { 1166 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); 1167 added = false; 1168 } else { 1169 Uri uri = intent.getData(); 1170 if (uri == null) { 1171 return; 1172 } 1173 String pkgName = uri.getSchemeSpecificPart(); 1174 if (pkgName == null) { 1175 return; 1176 } 1177 pkgList = new String[] { pkgName }; 1178 added = Intent.ACTION_PACKAGE_ADDED.equals(action); 1179 } 1180 if (pkgList == null || pkgList.length == 0) { 1181 return; 1182 } 1183 if (added) { 1184 synchronized (mAppWidgetIds) { 1185 Bundle extras = intent.getExtras(); 1186 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { 1187 for (String pkgName : pkgList) { 1188 // The package was just upgraded 1189 updateProvidersForPackageLocked(pkgName); 1190 } 1191 } else { 1192 // The package was just added 1193 for (String pkgName : pkgList) { 1194 addProvidersForPackageLocked(pkgName); 1195 } 1196 } 1197 saveStateLocked(); 1198 } 1199 } else { 1200 Bundle extras = intent.getExtras(); 1201 if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { 1202 // The package is being updated. We'll receive a PACKAGE_ADDED shortly. 1203 } else { 1204 synchronized (mAppWidgetIds) { 1205 for (String pkgName : pkgList) { 1206 removeProvidersForPackageLocked(pkgName); 1207 saveStateLocked(); 1208 } 1209 } 1210 } 1211 } 1212 } 1213 } 1214 }; 1215 1216 void addProvidersForPackageLocked(String pkgName) { 1217 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 1218 intent.setPackage(pkgName); 1219 List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, 1220 PackageManager.GET_META_DATA); 1221 1222 final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 1223 for (int i=0; i<N; i++) { 1224 ResolveInfo ri = broadcastReceivers.get(i); 1225 ActivityInfo ai = ri.activityInfo; 1226 if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 1227 continue; 1228 } 1229 if (pkgName.equals(ai.packageName)) { 1230 addProviderLocked(ri); 1231 } 1232 } 1233 } 1234 1235 void updateProvidersForPackageLocked(String pkgName) { 1236 HashSet<String> keep = new HashSet<String>(); 1237 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 1238 intent.setPackage(pkgName); 1239 List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, 1240 PackageManager.GET_META_DATA); 1241 1242 // add the missing ones and collect which ones to keep 1243 int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); 1244 for (int i=0; i<N; i++) { 1245 ResolveInfo ri = broadcastReceivers.get(i); 1246 ActivityInfo ai = ri.activityInfo; 1247 if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 1248 continue; 1249 } 1250 if (pkgName.equals(ai.packageName)) { 1251 ComponentName component = new ComponentName(ai.packageName, ai.name); 1252 Provider p = lookupProviderLocked(component); 1253 if (p == null) { 1254 if (addProviderLocked(ri)) { 1255 keep.add(ai.name); 1256 } 1257 } else { 1258 Provider parsed = parseProviderInfoXml(component, ri); 1259 if (parsed != null) { 1260 keep.add(ai.name); 1261 // Use the new AppWidgetProviderInfo. 1262 p.info = parsed.info; 1263 // If it's enabled 1264 final int M = p.instances.size(); 1265 if (M > 0) { 1266 int[] appWidgetIds = getAppWidgetIds(p); 1267 // Reschedule for the new updatePeriodMillis (don't worry about handling 1268 // it specially if updatePeriodMillis didn't change because we just sent 1269 // an update, and the next one will be updatePeriodMillis from now). 1270 cancelBroadcasts(p); 1271 registerForBroadcastsLocked(p, appWidgetIds); 1272 // If it's currently showing, call back with the new AppWidgetProviderInfo. 1273 for (int j=0; j<M; j++) { 1274 AppWidgetId id = p.instances.get(j); 1275 id.views = null; 1276 if (id.host != null && id.host.callbacks != null) { 1277 try { 1278 id.host.callbacks.providerChanged(id.appWidgetId, p.info); 1279 } catch (RemoteException ex) { 1280 // It failed; remove the callback. No need to prune because 1281 // we know that this host is still referenced by this 1282 // instance. 1283 id.host.callbacks = null; 1284 } 1285 } 1286 } 1287 // Now that we've told the host, push out an update. 1288 sendUpdateIntentLocked(p, appWidgetIds); 1289 } 1290 } 1291 } 1292 } 1293 } 1294 1295 // prune the ones we don't want to keep 1296 N = mInstalledProviders.size(); 1297 for (int i=N-1; i>=0; i--) { 1298 Provider p = mInstalledProviders.get(i); 1299 if (pkgName.equals(p.info.provider.getPackageName()) 1300 && !keep.contains(p.info.provider.getClassName())) { 1301 removeProviderLocked(i, p); 1302 } 1303 } 1304 } 1305 1306 void removeProvidersForPackageLocked(String pkgName) { 1307 int N = mInstalledProviders.size(); 1308 for (int i=N-1; i>=0; i--) { 1309 Provider p = mInstalledProviders.get(i); 1310 if (pkgName.equals(p.info.provider.getPackageName())) { 1311 removeProviderLocked(i, p); 1312 } 1313 } 1314 1315 // Delete the hosts for this package too 1316 // 1317 // By now, we have removed any AppWidgets that were in any hosts here, 1318 // so we don't need to worry about sending DISABLE broadcasts to them. 1319 N = mHosts.size(); 1320 for (int i=N-1; i>=0; i--) { 1321 Host host = mHosts.get(i); 1322 if (pkgName.equals(host.packageName)) { 1323 deleteHostLocked(host); 1324 } 1325 } 1326 } 1327} 1328 1329