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.omadm.plugin.impl; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.pm.ResolveInfo; 24import android.os.IBinder; 25import android.os.RemoteException; 26import android.text.TextUtils; 27import android.util.Log; 28 29import com.android.omadm.plugin.DmtData; 30import com.android.omadm.plugin.DmtException; 31import com.android.omadm.plugin.DmtPluginNode; 32import com.android.omadm.plugin.ErrorCodes; 33import com.android.omadm.plugin.IDmtPlugin; 34 35import java.util.ArrayList; 36import java.util.Arrays; 37import java.util.HashMap; 38import java.util.List; 39import java.util.Map; 40 41/** 42 * This class does not manage DMT plugins. It is a proxy between native plugin and java plugins. 43 * An instance of the class is created for each plugin. 44 */ 45public final class DmtPluginManager { 46 47 private static final String TAG = "DM_DmtPluginManager"; 48 private static final boolean DBG = false; 49 50 private static Context sContext; 51 52 /* Parameters of the plug-in */ 53 private String mPath; 54 private String mUid; 55 private String mServerID; // FIXME: mServerID is never set, remove? 56 private Map<String, String> mParameters; 57 58 private final class DmtServiceConnection implements ServiceConnection { 59 60 DmtServiceConnection() { 61 } 62 63 @Override 64 public synchronized void onServiceConnected(ComponentName className, IBinder service) { 65 logd("Enter onServiceConnected..."); 66 logd("Class:" + className + " service:" + service); 67 68 mPluginConnection = IDmtPlugin.Stub.asInterface(service); 69 70 try { 71 notifyAll(); 72 } catch (Exception e) { 73 loge("onServiceConnected(): exception", e); 74 } 75 } 76 77 @Override 78 public void onServiceDisconnected(ComponentName className) { 79 logd("Plug-in service disconnected. className:" + className); 80 mPluginConnection = null; 81 } 82 83 public synchronized void waitForConnection() { 84 try { 85 Intent intent = new Intent(mUid); 86 intent.putExtra("rootPath", mPath); 87 boolean currentPackageMatch = false; 88 List<ResolveInfo> intentServices = sContext.getPackageManager() 89 .queryIntentServices(intent, 0); 90 if (intentServices != null) { 91 for (ResolveInfo resolveInfo : intentServices) { 92 logd("getPackageName is: " + sContext.getPackageName()); 93 logd("resolveInfo.serviceInfo.packageName is: " 94 + resolveInfo.serviceInfo.packageName); 95 if(resolveInfo.serviceInfo.packageName.equals(sContext.getPackageName())) { 96 try { 97 Class.forName(mUid); 98 } catch (ClassNotFoundException e1) { 99 loge("ClassNotFoundException in waitForConnection", e1); 100 currentPackageMatch = true; 101 } 102 } 103 } 104 } 105 106 if (currentPackageMatch) { 107 return; 108 } 109 110 if (DBG) logd("Calling bindService: uid=\"" + mUid + "\" path=\"" + mPath + '"'); 111 sContext.bindService(intent, mConnector,Context.BIND_AUTO_CREATE); 112 113 wait(10000); // FIXME: wait not in loop! 114 if (DBG) logd("Waiting is finished"); 115 } catch (Exception e) { 116 loge("exception in waitForConnection", e); 117 } 118 } 119 } 120 121 private final DmtServiceConnection mConnector = new DmtServiceConnection(); 122 123 private IDmtPlugin mPluginConnection; 124 125 public DmtPluginManager() { 126 logd("DmtPluginManager.java constructor..."); 127 } 128 129 public static void setContext(Context context) { 130 if (DBG) logd("Enter setContext(" + context + ')'); 131 132 if (context == null) { 133 logd("Context is null!"); 134 return; 135 } 136 137 Context appContext = context.getApplicationContext(); 138 139 if (DBG) logd("app context is: " + appContext); 140 141 sContext = appContext; 142 } 143 144 /** 145 * Initialize Java plugin. Called from JNI in: 146 * engine/javaplugin/nativelib/src/DmtJavaPluginManager.cc 147 * 148 * @param path the root path of the plug-in. 149 * @param parameters initial parameters of the plug-in, as an array of strings. 150 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 151 */ 152 public boolean initJavaPlugin(String path, String[] parameters) { 153 if (DBG) logd("Enter initJavaPlugin: path = " + path); 154 if (DBG) logd("parameters are: " + Arrays.toString(parameters)); 155 156 if (TextUtils.isEmpty(path)) { 157 loge("Invalid path!...."); 158 return false; 159 } 160 161 if (mPath != null) { 162 loge("Plugin already loaded!...."); 163 return false; 164 } 165 166 if (DBG) logd("Parameters count is " + parameters.length); 167 if (parameters.length % 2 != 0) { 168 loge("Parameter count is not right."); 169 return false; 170 } 171 172 // FIXME: replace HashTable with HashMap 173 Map<String, String> params = new HashMap<String, String>(); 174 for (int i = 0; i < parameters.length; i += 2 ) { 175 params.put(parameters[i], parameters[i + 1]); 176 177 if ("_uid".equals(parameters[i])) { 178 mUid = parameters[i + 1]; 179 } 180 } 181 182 if (TextUtils.isEmpty(mUid)) { 183 loge("uid is empty..."); 184 return false; 185 } 186 187 mParameters = params; 188 mPath = path; 189 190 return bindPluginService(); 191 } 192 193 /** 194 * Performs "Execute" command on a plug-in node. 195 * Called from JNI. 196 * 197 * @param args exec plug-in arguments. 198 * @param correlator correlator. 199 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 200 */ 201 public int executeNode(String args, String correlator) { 202 if (DBG) logd("Enter executeNode(\"" + args + "\", \"" + correlator + "\")"); 203 204 if (mPluginConnection == null) { 205 loge("There is no bound plug-in"); 206 return ErrorCodes.SYNCML_DM_FAIL; 207 } 208 209 try { 210 return mPluginConnection.exec(mPath, args, correlator); 211 } catch (Exception e) { 212 loge("Exception in executeNode", e); 213 return ErrorCodes.SYNCML_DM_FAIL; 214 } 215 } 216 217 /** 218 * Performs commit operation under current DMT. 219 * Called from JNI. 220 * 221 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 222 */ 223 public int commit() { 224 loge("Enter commit... " + mPath); 225 226 if (mPluginConnection == null) { 227 loge("There is no bound plug-in"); 228 return ErrorCodes.SYNCML_DM_FAIL; 229 } 230 231 try { 232 return mPluginConnection.commit(); 233 } catch (Exception e) { 234 loge("Exception in commit", e); 235 return ErrorCodes.SYNCML_DM_FAIL; 236 } 237 } 238 239 /** 240 * Sets Server ID of the plug-in. 241 * Called from JNI. 242 * 243 * @param serverID service ID. 244 */ 245 public void setServerID(String serverID) { 246 if (DBG) logd("Enter setServerID(\"" + serverID + "\")"); 247 248 if (mPluginConnection == null) { 249 loge("There is no bound plug-in"); 250 return; 251 } 252 253 try { 254 mPluginConnection.setServerID(serverID); 255 } catch (Exception e) { 256 loge("Exception in setServerID", e); 257 } 258 } 259 260 /** 261 * Creates an interior node in the tree for the specified path. 262 * Called from JNI. 263 * 264 * @param path full path to the node. 265 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 266 */ 267 public int createInteriorNode(String path) { 268 if (DBG) logd("Enter createInteriorNode(\"" + path + "\")"); 269 270 if (mPluginConnection == null) { 271 loge("There is no bound plug-in"); 272 return ErrorCodes.SYNCML_DM_FAIL; 273 } 274 275 try { 276 return mPluginConnection.createInteriorNode(getFullPath(path)); 277 } catch (Exception e) { 278 loge("Exception in createInteriorNode", e); 279 return ErrorCodes.SYNCML_DM_FAIL; 280 } 281 } 282 283 /** 284 * Creates a leaf node in the tree by given path. 285 * Called from JNI. 286 * 287 * @param path full path to the node. 288 * @param type type of node, defined as constants in {@link DmtData}. 289 * @param value the new value to set. 290 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 291 */ 292 public int createLeafNode(String path, int type, String value) { 293 if (DBG) logd("Enter createLeafNode(\"" + path + "\", " + type + ", \"" + value + "\")"); 294 295 if (mPluginConnection == null) { 296 loge("There is no bound plug-in"); 297 return ErrorCodes.SYNCML_DM_FAIL; 298 } 299 300 if (type < DmtData.NULL || type >= DmtData.NODE) { 301 loge("Invalid data type: " + type); 302 return ErrorCodes.SYNCML_DM_FAIL; 303 } 304 305 DmtData data = new DmtData(value, type); 306 307 try { 308 return mPluginConnection.createLeafNode(getFullPath(path), data); 309 } catch (Exception e) { 310 loge("Exception in createLeafNode", e); 311 return ErrorCodes.SYNCML_DM_FAIL; 312 } 313 } 314 315 /** 316 * Renames a node. 317 * Called from JNI. 318 * 319 * NOTE: This is an optional command of the OMA DM protocol. 320 * Currently java plug-ins do not support the command. 321 * 322 * @param path full path to node to be renamed. 323 * @param newNodeName new node name. 324 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 325 */ 326 public int renameNode(String path, String newNodeName) { 327 if (DBG) logd("Enter renameNode(\"" + path + "\", \"" + newNodeName + "\")"); 328 329 if (mPluginConnection == null) { 330 loge("There is no bound plug-in"); 331 return ErrorCodes.SYNCML_DM_FAIL; 332 } 333 334 if (TextUtils.isEmpty(newNodeName)) { 335 loge("Invalid new node name!"); 336 return ErrorCodes.SYNCML_DM_FAIL; 337 } 338 339 try { 340 return mPluginConnection.renameNode(getFullPath(path), newNodeName); 341 } catch (Exception e) { 342 loge("Exception in renameNode", e); 343 return ErrorCodes.SYNCML_DM_FAIL; 344 } 345 } 346 347 /** 348 * Deletes a node with the specified path. 349 * Called from JNI. 350 * 351 * @param path full path to the node. 352 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 353 */ 354 public int deleteNode(String path) { 355 if (DBG) logd("Enter deleteNode(\"" + path + "\")"); 356 357 if (mPluginConnection == null) { 358 loge("There is no bound plug-in"); 359 return ErrorCodes.SYNCML_DM_FAIL; 360 } 361 362 try { 363 return mPluginConnection.deleteNode(getFullPath(path)); 364 } catch (Exception e) { 365 loge("Exception in deleteNode", e); 366 return ErrorCodes.SYNCML_DM_FAIL; 367 } 368 } 369 370 /** 371 * Set new value for the specified node. 372 * Called from JNI. 373 * 374 * @param path full path to the node. 375 * @param type type of node, defined as constants in {@link DmtData}. 376 * @param value the new value to set. 377 * @return {@link ErrorCodes#SYNCML_DM_SUCCESS} on success, error code on failure. 378 */ 379 public int setNodeValue(String path, int type, String value) { 380 if (DBG) logd("Enter setNodeValue(\"" + path + "\", " + type + ", \"" + value + "\")"); 381 382 if (mPluginConnection == null) { 383 loge("There is no bound plug-in"); 384 return ErrorCodes.SYNCML_DM_FAIL; 385 } 386 387 if (type < DmtData.NULL || type >= DmtData.NODE) { 388 loge("Invalid data type: " + type); 389 return ErrorCodes.SYNCML_DM_FAIL; 390 } 391 392 DmtData data = new DmtData(value, type); 393 394 try { 395 logd("Update leaf node: path = " + path + ", data = " + data.getString()); 396 return mPluginConnection.updateLeafNode(getFullPath(path), data); 397 } catch (Exception e) { 398 loge("Exception in setNodeValue", e); 399 return ErrorCodes.SYNCML_DM_FAIL; 400 } 401 } 402 403 /** 404 * Returns value of leaf node for the specified path. 405 * Called from JNI. 406 * 407 * @param path path to the leaf node. 408 * @return String array where the first element is value type and the second is the value. 409 * @throws DmtException in case of error. 410 */ 411 public String[] getNodeValue(String path) throws DmtException { 412 if (DBG) logd("Enter getNodeValue(\"" + path + "\")"); 413 414 if (mPluginConnection == null) { 415 loge("There is no bound plug-in"); 416 throw new DmtException("There is no bound plug-in"); 417 } 418 419 DmtData data; 420 try { 421 data = mPluginConnection.getNodeValue(getFullPath(path)); 422 } catch (Exception e) { 423 loge("Exception in getNodeValue", e); 424 throw new DmtException(e.getMessage()); 425 } 426 427 if (data == null) { 428 int retcode; 429 try { 430 retcode = mPluginConnection.getOperationResult(); 431 } catch (Exception e) { 432 loge("Exception in getNodeValue", e); 433 throw new DmtException(e.getMessage()); 434 } 435 436 if (retcode == ErrorCodes.SYNCML_DM_SUCCESS) { 437 loge("Invalid plug-in implementation!"); 438 throw new DmtException("Invalid plug-in implementation!"); 439 } 440 /* 441 * Added this block to return Error code to DM Engine since 442 * throwing an exception is causing a VM error and aborting 443 * the app. 444 */ 445 else if (retcode == ErrorCodes.SYNCML_DM_UNSUPPORTED_OPERATION) { 446 loge("Get feature not implemented on this node"); 447 String[] errString = new String[1]; 448 errString[0] = Integer.toString(retcode); 449 return errString; 450 } 451 452 else { 453 loge("Error occurred while doing a get on this node"); 454 String[] errString = new String[1]; 455 errString[0] = Integer.toString(retcode); 456 return errString; 457 } 458 459 //throw new DmtException(retcode, "Value is not set"); 460 } 461 462 int dataType = data.getType(); 463 464 switch (dataType) { 465 case DmtData.NULL: 466 case DmtData.STRING: 467 case DmtData.INT: 468 case DmtData.BOOL: 469 case DmtData.BIN: 470 case DmtData.DATE: 471 case DmtData.TIME: 472 case DmtData.FLOAT: 473 String value = data.getString(); 474 String[] resStrArr = new String[2]; 475 resStrArr[0] = Integer.toString(dataType); 476 resStrArr[1] = TextUtils.isEmpty(value) ? "" : value; 477 return resStrArr; 478 479 case DmtData.NODE: 480 throw new DmtException("Operation not allowed for interior node!"); 481 482 default: 483 throw new DmtException("Invalid node type"); 484 } 485 } 486 487 /** 488 * Gets a set of nodes for the specified path. 489 * Called from JNI. 490 * 491 * If the plug-in cannot return the set, null value shall be returned by 492 * the method and DM engine will use the getOperationResult() method 493 * to get result of the operation. 494 * 495 * @return a String array containing triples of (key, type, value) as Strings 496 * @throws DmtException on any exception 497 */ 498 public String[] getNodes() throws DmtException { 499 if (DBG) logd("Enter getNodes..."); 500 501 if (mPluginConnection == null) { 502 loge("There is no bound plug-in"); 503 throw new DmtException("There is no bound plug-in"); 504 } 505 506 Map<String, DmtPluginNode> pluginNodes; 507 try { 508 pluginNodes = (Map<String, DmtPluginNode>) mPluginConnection.getNodes(mPath); 509 } catch (RemoteException e) { 510 loge("RemoteException in getNodes", e); 511 throw new DmtException(e.getMessage()); 512 } 513 514 if (pluginNodes == null) { 515 int retcode; 516 try { 517 retcode = mPluginConnection.getOperationResult(); 518 } catch (Exception e) { 519 loge("Exception in getNodes", e); 520 throw new DmtException(e.getMessage()); 521 } 522 523 if (retcode == ErrorCodes.SYNCML_DM_SUCCESS) { 524 loge("Invalid plug-in implementation!"); 525 throw new DmtException("Invalid plug-in implementation!"); 526 } 527 528 throw new DmtException(retcode, "Value is not set"); 529 } 530 531 if (pluginNodes.isEmpty()) { 532 // FIXME: zero-length array constructed 533 return new String[0]; 534 } 535 536 if (DBG) logd("Data plugin has " + pluginNodes.size() + " nodes."); 537 String[] resStrArr = new String[pluginNodes.size() * 3]; 538 539 int i = 0; 540 for (String key : pluginNodes.keySet()) { 541 if (DBG) logd("No." + i + " : " + key); 542 DmtPluginNode tmpNode = pluginNodes.get(key); 543 if (tmpNode == null) { 544 throw new DmtException("Invalid map of all nodes"); 545 } 546 resStrArr[i] = getRelativePath(key); 547 resStrArr[i + 1] = Integer.toString(tmpNode.getType()); 548 switch (tmpNode.getType()) { 549 case DmtData.NODE: 550 StringBuilder tmpSB = new StringBuilder(""); 551 DmtData data = tmpNode.getValue(); 552 if (data != null) { 553 Map<String, DmtData> childNodes = data.getChildNodeMap(); 554 for (String subNodeName : childNodes.keySet()) { 555 if (TextUtils.isEmpty(subNodeName)) { 556 loge("invalid interior node value for " + subNodeName + "!!!"); 557 throw new DmtException("Invalid interior node value"); 558 } else { 559 tmpSB.append(subNodeName).append('\n'); 560 } 561 } 562 } 563 resStrArr[i + 2] = tmpSB.toString(); 564 break; 565 566 case DmtData.NULL: 567 case DmtData.STRING: 568 case DmtData.INT: 569 case DmtData.BOOL: 570 case DmtData.BIN: 571 case DmtData.DATE: 572 case DmtData.TIME: 573 case DmtData.FLOAT: 574 resStrArr[i + 2] = ""; 575 break; 576 577 default: 578 loge("invalid node type " + tmpNode.getType() + "!!!!"); 579 throw new DmtException("Invalid node type"); 580 } 581 i += 3; 582 } 583 return resStrArr; 584 } 585 586 public void release() { 587 if (DBG) logd("Enter release... " + mPath); 588 589 if (mPluginConnection == null) { 590 return; 591 } 592 593 try { 594 mPluginConnection.release(); 595 } catch (Exception e) { 596 loge("exception releasing plugin", e); 597 } 598 599 mPluginConnection = null; 600 601 try { 602 sContext.unbindService(mConnector); 603 } catch (Exception e) { 604 loge("unbindService exception in release", e); 605 } 606 } 607 608 private boolean bindPluginService() { 609 if (DBG) logd("Enter bindPluginService..."); 610 611 if (sContext == null) { 612 loge("Undefined context!"); 613 return false; 614 } 615 616 if (mPluginConnection != null) { 617 loge("Already bound!"); 618 return true; 619 } 620 621 try { 622 logd("uid = " + mUid); 623 logd("rootPath = " + mPath); 624 625 mConnector.waitForConnection(); 626 627 if (mPluginConnection == null) { 628 loge("Impossible to bind to plug-in!..."); 629 sContext.unbindService(mConnector); 630 return false; 631 } 632 633 if (DBG) logd("context = " + sContext.toString()); 634 635 // FIXME: mServerID is never set, remove this? 636 if (mServerID != null) { 637 mPluginConnection.setServerID(mServerID); 638 } 639 640 return mPluginConnection.init(mPath, mParameters); 641 } catch (Exception e) { 642 loge("bindPluginService: Unable to get service " + mUid, e); 643 return false; 644 } 645 } 646 647 private String getRelativePath(String path) { 648 if (TextUtils.isEmpty(path) || path.equals(mPath)) { 649 return ""; 650 } 651 652 if (path.startsWith(mPath + '/')) { 653 return path.substring(mPath.length() + 1); 654 } 655 656 return path; 657 } 658 659 private String getFullPath(String path) { 660 if (TextUtils.isEmpty(path)) { 661 return mPath; 662 } 663 664 if (path.startsWith("./")) { 665 return path; 666 } 667 668 return mPath + '/' + path; 669 } 670 671 private static void logd(String msg) { 672 Log.d(TAG, msg); 673 } 674 675 private static void loge(String msg) { 676 Log.e(TAG, msg); 677 } 678 679 private static void loge(String msg, Throwable tr) { 680 Log.e(TAG, msg, tr); 681 } 682} 683