1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chromoting; 6 7 import android.app.Activity; 8 import android.text.TextUtils; 9 import android.util.Log; 10 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.List; 14 15 /** 16 * A manager for the capabilities of the Android client. Based on the negotiated set of 17 * capabilities, it creates the associated ClientExtensions, and enables their communication with 18 * the Chromoting host by dispatching extension messages appropriately. 19 * 20 * The CapabilityManager mirrors how the Chromoting host handles extension messages. For each 21 * incoming extension message, runs through a list of HostExtensionSession objects, giving each one 22 * a chance to handle the message. 23 * 24 * The CapabilityManager is a singleton class so we can manage client extensions on an application 25 * level. The singleton object may be used from multiple Activities, thus allowing it to support 26 * different capabilities at different stages of the application. 27 */ 28 public class CapabilityManager { 29 30 /** Lazily-initialized singleton object that can be used from different Activities. */ 31 private static CapabilityManager sInstance; 32 33 /** Protects access to |sInstance|. */ 34 private static final Object sInstanceLock = new Object(); 35 36 /** List of all capabilities that are supported by the application. */ 37 private List<String> mLocalCapabilities; 38 39 /** List of negotiated capabilities received from the host. */ 40 private List<String> mNegotiatedCapabilities; 41 42 /** List of extensions to the client based on capabilities negotiated with the host. */ 43 private List<ClientExtension> mClientExtensions; 44 45 private CapabilityManager() { 46 mLocalCapabilities = new ArrayList<String>(); 47 mClientExtensions = new ArrayList<ClientExtension>(); 48 49 mLocalCapabilities.add(Capabilities.CAST_CAPABILITY); 50 } 51 52 /** 53 * Returns the singleton object. Thread-safe. 54 */ 55 public static CapabilityManager getInstance() { 56 synchronized (sInstanceLock) { 57 if (sInstance == null) { 58 sInstance = new CapabilityManager(); 59 } 60 return sInstance; 61 } 62 } 63 64 /** 65 * Returns a space-separated list (required by host) of the capabilities supported by 66 * this client. 67 */ 68 public String getLocalCapabilities() { 69 return TextUtils.join(" ", mLocalCapabilities); 70 } 71 72 /** 73 * Returns the ActivityLifecycleListener associated with the specified capability, if 74 * |capability| is enabled and such a listener exists. 75 * 76 * Activities that call this method agree to appropriately notify the listener of lifecycle 77 * events., thus supporting |capability|. This allows extensions like the CastExtensionHandler 78 * to hook into an existing activity's lifecycle. 79 */ 80 public ActivityLifecycleListener onActivityAcceptingListener( 81 Activity activity, String capability) { 82 83 ActivityLifecycleListener listener; 84 85 if (isCapabilityEnabled(capability)) { 86 for (ClientExtension ext : mClientExtensions) { 87 if (ext.getCapability().equals(capability)) { 88 listener = ext.onActivityAcceptingListener(activity); 89 if (listener != null) 90 return listener; 91 } 92 } 93 } 94 95 return new DummyActivityLifecycleListener(); 96 } 97 98 /** 99 * Receives the capabilities negotiated between client and host and creates the appropriate 100 * extension handlers. 101 * 102 * Currently only the CAST_CAPABILITY exists, so that is the only extension constructed. 103 */ 104 public void setNegotiatedCapabilities(String capabilities) { 105 mNegotiatedCapabilities = Arrays.asList(capabilities.split(" ")); 106 mClientExtensions.clear(); 107 if (isCapabilityEnabled(Capabilities.CAST_CAPABILITY)) { 108 mClientExtensions.add(maybeCreateCastExtensionHandler()); 109 } 110 } 111 112 /** 113 * Passes the deconstructed extension message to each ClientExtension in turn until the message 114 * is handled or none remain. Returns true if the message was handled. 115 */ 116 public boolean onExtensionMessage(String type, String data) { 117 if (type == null || type.isEmpty()) { 118 return false; 119 } 120 121 for (ClientExtension ext : mClientExtensions) { 122 if (ext.onExtensionMessage(type, data)) { 123 return true; 124 } 125 } 126 return false; 127 } 128 129 /** 130 * Return true if the capability is enabled for this connection with the host. 131 */ 132 private boolean isCapabilityEnabled(String capability) { 133 return (mNegotiatedCapabilities != null && mNegotiatedCapabilities.contains(capability)); 134 } 135 136 /** 137 * Tries to reflectively instantiate a CastExtensionHandler object. 138 * 139 * Note: The ONLY reason this is done is that by default, the regular android application 140 * will be built, without this experimental extension. 141 */ 142 private ClientExtension maybeCreateCastExtensionHandler() { 143 try { 144 Class<?> cls = Class.forName("org.chromium.chromoting.CastExtensionHandler"); 145 return (ClientExtension) cls.newInstance(); 146 } catch (ClassNotFoundException e) { 147 Log.w("CapabilityManager", "Failed to create CastExtensionHandler."); 148 return new DummyClientExtension(); 149 } catch (InstantiationException e) { 150 Log.w("CapabilityManager", "Failed to create CastExtensionHandler."); 151 return new DummyClientExtension(); 152 } catch (IllegalAccessException e) { 153 Log.w("CapabilityManager", "Failed to create CastExtensionHandler."); 154 return new DummyClientExtension(); 155 } 156 } 157 } 158