Java tutorial
/* DConnectMessageService.java Copyright (c) 2014 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.manager; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import org.deviceconnect.android.event.Event; import org.deviceconnect.android.event.EventManager; import org.deviceconnect.android.event.cache.MemoryCacheController; import org.deviceconnect.android.localoauth.CheckAccessTokenResult; import org.deviceconnect.android.localoauth.ClientPackageInfo; import org.deviceconnect.android.localoauth.LocalOAuth2Main; import org.deviceconnect.android.logger.AndroidHandler; import org.deviceconnect.android.manager.DConnectLocalOAuth.OAuthData; import org.deviceconnect.android.manager.DevicePluginManager.DevicePluginEventListener; import org.deviceconnect.android.manager.hmac.HmacManager; import org.deviceconnect.android.manager.policy.OriginParser; import org.deviceconnect.android.manager.policy.Whitelist; import org.deviceconnect.android.manager.profile.AuthorizationProfile; import org.deviceconnect.android.manager.profile.DConnectAvailabilityProfile; import org.deviceconnect.android.manager.profile.DConnectDeliveryProfile; import org.deviceconnect.android.manager.profile.DConnectFilesProfile; import org.deviceconnect.android.manager.profile.DConnectServiceDiscoveryProfile; import org.deviceconnect.android.manager.profile.DConnectSystemProfile; import org.deviceconnect.android.manager.request.DConnectRequest; import org.deviceconnect.android.manager.request.DConnectRequestManager; import org.deviceconnect.android.manager.request.DiscoveryDeviceRequest; import org.deviceconnect.android.manager.request.RegisterNetworkServiceDiscovery; import org.deviceconnect.android.manager.setting.SettingActivity; import org.deviceconnect.android.manager.util.DConnectUtil; import org.deviceconnect.android.message.MessageUtils; import org.deviceconnect.android.profile.DConnectProfile; import org.deviceconnect.android.profile.DConnectProfileProvider; import org.deviceconnect.android.profile.ServiceDiscoveryProfile; import org.deviceconnect.android.provider.FileManager; import org.deviceconnect.message.DConnectMessage; import org.deviceconnect.message.intent.message.IntentDConnectMessage; import org.deviceconnect.profile.ServiceDiscoveryProfileConstants; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; /** * DConnectMessage??. * @author NTT DOCOMO, INC. */ public abstract class DConnectMessageService extends Service implements DConnectProfileProvider, DevicePluginEventListener { /** ??. */ private static final String DCONNECT_DOMAIN = ".deviceconnect.org"; /** ???. */ private static final String LOCALHOST_DCONNECT = "localhost" + DCONNECT_DOMAIN; /** file?. */ private static final String ORIGIN_FILE = "file://"; /** ???. */ private static final String[] IGNORED_ORIGINS = { ORIGIN_FILE }; /** Notification ID.*/ private static final int ONGOING_NOTIFICATION_ID = 4035; /** ID?. */ public static final String SEPARATOR = "."; /** ?receiver?. */ public static final String SEPARATOR_SESSION = "@"; /** ?. */ private static final int ERROR_CODE = Integer.MIN_VALUE; /** URI??. */ private static final String SCHEME_LAUNCH = "dconnect"; /** . */ protected final Logger mLogger = Logger.getLogger("dconnect.manager"); /** dConnect Manager???. */ private String mDConnectDomain = LOCALHOST_DCONNECT; /** . */ private Map<String, DConnectProfile> mProfileMap = new HashMap<String, DConnectProfile>(); /** ???. */ private DConnectProfile mDeliveryProfile; /** ?. */ protected DConnectRequestManager mRequestManager; /** ??. */ protected DevicePluginManager mPluginMgr; /** DeviceConnect?. */ protected DConnectSettings mSettings; /** ??. */ protected FileManager mFileMgr; /** Local OAuth???. */ private DConnectLocalOAuth mLocalOAuth; /** HMAC?. */ private HmacManager mHmacManager; /** ?. */ private Whitelist mWhitelist; /** ??. */ protected boolean mRunningFlag; @Override public IBinder onBind(final Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); if (BuildConfig.DEBUG) { AndroidHandler handler = new AndroidHandler("dconnect.manager"); handler.setFormatter(new SimpleFormatter()); handler.setLevel(Level.ALL); mLogger.addHandler(handler); mLogger.setLevel(Level.ALL); } else { mLogger.setLevel(Level.OFF); } // ??? EventManager.INSTANCE.setController(new MemoryCacheController()); // Local OAuth?? LocalOAuth2Main.initialize(getApplicationContext()); // DConnect mSettings = DConnectSettings.getInstance(); mSettings.load(this); // ? mFileMgr = new FileManager(this); // ???Local OAuth mLocalOAuth = new DConnectLocalOAuth(this); // ???? mPluginMgr = new DevicePluginManager(this, mDConnectDomain); mPluginMgr.setEventListener(this); // ? addProfile(new AuthorizationProfile()); addProfile(new DConnectAvailabilityProfile()); addProfile(new DConnectServiceDiscoveryProfile(this, mPluginMgr)); addProfile(new DConnectFilesProfile(this)); addProfile(new DConnectSystemProfile(this, mPluginMgr)); // dConnect Manager???????????? setDeliveryProfile(new DConnectDeliveryProfile(mPluginMgr, mLocalOAuth, mSettings.requireOrigin())); } @Override public void onDestroy() { stopDConnect(); LocalOAuth2Main.destroy(); super.onDestroy(); } @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { super.onStartCommand(intent, flags, startId); if (intent == null) { mLogger.warning("intent is null."); return START_STICKY; } if (!mRunningFlag) { return START_STICKY; } String action = intent.getAction(); if (action == null) { mLogger.warning("action is null."); return START_STICKY; } String scheme = intent.getScheme(); if (SCHEME_LAUNCH.equals(scheme)) { String key = intent.getStringExtra(IntentDConnectMessage.EXTRA_KEY); String origin = intent.getStringExtra(IntentDConnectMessage.EXTRA_ORIGIN); if (key != null && !TextUtils.isEmpty(origin)) { mHmacManager.updateKey(origin, key); } return START_STICKY; } if (checkAction(action)) { onRequestReceive(intent); } else if (IntentDConnectMessage.ACTION_RESPONSE.equals(action)) { onResponseReceive(intent); } else if (IntentDConnectMessage.ACTION_EVENT.equals(action)) { onEventReceive(intent); } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { mPluginMgr.checkAndAddDevicePlugin(intent); } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { mPluginMgr.checkAndRemoveDevicePlugin(intent); } return START_STICKY; } /** * Intent?????????. * @param request Intent */ public void onRequestReceive(final Intent request) { // ????????? int requestCode = request.getIntExtra(IntentDConnectMessage.EXTRA_REQUEST_CODE, ERROR_CODE); if (requestCode == ERROR_CODE) { mLogger.warning("Illegal requestCode in onRequestReceive. requestCode=" + requestCode); return; } // ???????? if (mFileMgr != null) { mFileMgr.checkAndRemove(); } // ??Intent?? Intent response = new Intent(IntentDConnectMessage.ACTION_RESPONSE); response.putExtra(DConnectMessage.EXTRA_RESULT, DConnectMessage.RESULT_ERROR); response.putExtra(DConnectMessage.EXTRA_REQUEST_CODE, requestCode); // ?? String profileName = request.getStringExtra(DConnectMessage.EXTRA_PROFILE); OriginError error = checkOrigin(request); switch (error) { case NOT_SPECIFIED: MessageUtils.setInvalidOriginError(response, "Origin is not specified."); sendResponse(request, response); return; case NOT_UNIQUE: MessageUtils.setInvalidOriginError(response, "The specified origin is not unique."); sendResponse(request, response); return; case NOT_ALLOWED: // NOTE: Local OAuth?API?? DConnectProfile profile = getProfile(profileName); if (profile != null && profile instanceof AuthorizationProfile) { ((AuthorizationProfile) profile).onInvalidOrigin(request, response); } MessageUtils.setInvalidOriginError(response, "The specified origin is not allowed."); sendResponse(request, response); return; default: break; } if (profileName == null) { MessageUtils.setNotSupportProfileError(response); sendResponse(request, response); return; } if (mSettings.isUseALocalOAuth()) { // ?? String accessToken = request.getStringExtra(AuthorizationProfile.PARAM_ACCESS_TOKEN); CheckAccessTokenResult result = LocalOAuth2Main.checkAccessToken(accessToken, profileName, DConnectLocalOAuth.IGNORE_PROFILES); if (result.checkResult()) { executeRequest(request, response); } else { if (accessToken == null) { MessageUtils.setEmptyAccessTokenError(response); } else if (!result.isExistAccessToken()) { MessageUtils.setNotFoundClientId(response); } else if (!result.isExistClientId()) { MessageUtils.setNotFoundClientId(response); } else if (!result.isExistScope()) { MessageUtils.setScopeError(response); } else if (!result.isNotExpired()) { MessageUtils.setExpiredAccessTokenError(response); } else { MessageUtils.setAuthorizationError(response); } sendResponse(request, response); } } else { executeRequest(request, response); } } /** * ???. * <p> * ????OFF?????????? * </p> * @param request ?? * @return ???? */ private OriginError checkOrigin(final Intent request) { if (!mSettings.requireOrigin()) { return OriginError.NONE; } String originParam = request.getStringExtra(IntentDConnectMessage.EXTRA_ORIGIN); if (originParam == null) { return OriginError.NOT_SPECIFIED; } String[] origins = originParam.split(" "); if (origins.length != 1) { return OriginError.NOT_UNIQUE; } if (!allowsOrigin(request)) { return OriginError.NOT_ALLOWED; } return OriginError.NONE; } /** * ???. * @param response ?Intent */ public void onResponseReceive(final Intent response) { // ????????? int requestCode = response.getIntExtra(IntentDConnectMessage.EXTRA_REQUEST_CODE, ERROR_CODE); if (requestCode == ERROR_CODE) { mLogger.warning("Illegal requestCode in onResponseReceive. requestCode=" + requestCode); return; } // ???? mRequestManager.setResponse(response); } /** * ??. * @param event Intent */ public void onEventReceive(final Intent event) { String sessionKey = event.getStringExtra(DConnectMessage.EXTRA_SESSION_KEY); String serviceId = event.getStringExtra(DConnectMessage.EXTRA_SERVICE_ID); String profile = event.getStringExtra(DConnectMessage.EXTRA_PROFILE); String inter = event.getStringExtra(DConnectMessage.EXTRA_INTERFACE); String attribute = event.getStringExtra(DConnectMessage.EXTRA_ATTRIBUTE); if (BuildConfig.DEBUG) { mLogger.info(String.format( "onEventReceive: [sessionKey: %s serviceId: %s profile: %s inter: %s attribute: %s]", sessionKey, serviceId, profile, inter, attribute)); } if (sessionKey != null) { // ?receiver?? String receiver = null; int index = sessionKey.indexOf(SEPARATOR_SESSION); if (index > 0) { receiver = sessionKey.substring(index + 1); sessionKey = sessionKey.substring(0, index); } // ????ID? String pluginId = convertSessionKey2PluginId(sessionKey); String key = convertSessionKey2Key(sessionKey); DevicePlugin plugin = mPluginMgr.getDevicePlugin(pluginId); if (plugin == null) { mLogger.warning("plugin is null."); return; } String did = mPluginMgr.appendServiceId(plugin, serviceId); event.putExtra(DConnectMessage.EXTRA_SESSION_KEY, key); // Local OAuth????????clientId???? // ??? if (ServiceDiscoveryProfileConstants.PROFILE_NAME.equals(profile) || ServiceDiscoveryProfileConstants.ATTRIBUTE_ON_SERVICE_CHANGE.equals(attribute)) { // network service discovery?????networkService?????? Bundle service = (Bundle) event.getParcelableExtra(ServiceDiscoveryProfile.PARAM_NETWORK_SERVICE); String id = service.getString(ServiceDiscoveryProfile.PARAM_ID); did = mPluginMgr.appendServiceId(plugin, id); // ID replaceServiceId(event, plugin); OAuthData oauth = mLocalOAuth.getOAuthData(did); if (oauth == null && plugin != null) { createClientOfDevicePlugin(plugin, did, event); } else { // ??? List<Event> evts = EventManager.INSTANCE.getEventList(profile, attribute); for (int i = 0; i < evts.size(); i++) { Event evt = evts.get(i); event.putExtra(DConnectMessage.EXTRA_SESSION_KEY, evt.getSessionKey()); sendEvent(evt.getReceiverName(), event); } } } else { replaceServiceId(event, plugin); sendEvent(receiver, event); } } else { mLogger.warning("onEventReceive: sessionKey is null."); } } /** * ?. * @param request * @param response ? */ private void executeRequest(final Intent request, final Intent response) { // ?DeviceConnectManager?? request.putExtra(DConnectMessage.EXTRA_PRODUCT, getString(R.string.app_name)); request.putExtra(DConnectMessage.EXTRA_VERSION, DConnectUtil.getVersionName(this)); boolean send = false; String profileName = request.getStringExtra(DConnectMessage.EXTRA_PROFILE); DConnectProfile profile = getProfile(profileName); if (profile != null) { send = profile.onRequest(request, response); } if (!send) { sendDeliveryProfile(request, response); } } /** * ?ID???. * * * @param sessionkey * @return ID */ private String convertSessionKey2PluginId(final String sessionkey) { int index = sessionkey.lastIndexOf(SEPARATOR); if (index > 0) { return sessionkey.substring(index + 1); } return sessionkey; } /** * ???????????. * @param sessionkey * @return ? */ private String convertSessionKey2Key(final String sessionkey) { int index = sessionkey.lastIndexOf(SEPARATOR); if (index > 0) { return sessionkey.substring(0, index); } return sessionkey; } /** * ?. * @param request ? */ public void addRequest(final DConnectRequest request) { mRequestManager.addRequest(request); } /** * DConnectLocalOAuth???. * @return DConnectLocalOAuth? */ public DConnectLocalOAuth getLocalOAuth() { return mLocalOAuth; } @Override public List<DConnectProfile> getProfileList() { List<DConnectProfile> profileList = new ArrayList<DConnectProfile>(mProfileMap.values()); return profileList; } @Override public void addProfile(final DConnectProfile profile) { if (profile != null) { profile.setContext(this); mProfileMap.put(profile.getProfileName(), profile); } } @Override public void removeProfile(final DConnectProfile profile) { if (profile != null) { mProfileMap.remove(profile.getProfileName()); } } /** * ??????????????. * @param profile */ public void setDeliveryProfile(final DConnectProfile profile) { if (profile != null) { profile.setContext(this); mDeliveryProfile = profile; } } /** * ?????DConnectProfile??. * ?????DConnectProfile???????null?? * @param name ?? * @return DConnectProfile? */ public DConnectProfile getProfile(final String name) { if (name == null) { return null; } return mProfileMap.get(name); } @Override public void onDeviceFound(final DevicePlugin plugin) { RegisterNetworkServiceDiscovery req = new RegisterNetworkServiceDiscovery(); req.setContext(this); req.setSessionKey(plugin.getServiceId()); req.setDestination(plugin); req.setDevicePluginManager(mPluginMgr); addRequest(req); } @Override public void onDeviceLost(final DevicePlugin plugin) { mLocalOAuth.deleteOAuthDatas(plugin.getServiceId()); } /** * ???dConnect???. * @param action * @return dConnect????true, ???false */ private boolean checkAction(final String action) { return (action.equals(IntentDConnectMessage.ACTION_GET) || action.equals(IntentDConnectMessage.ACTION_PUT) || action.equals(IntentDConnectMessage.ACTION_POST) || action.equals(IntentDConnectMessage.ACTION_DELETE)); } /** * DConnectManager? */ protected synchronized void startDConnect() { mRunningFlag = true; // ? mSettings.load(this); if (BuildConfig.DEBUG) { mLogger.info("Settings"); mLogger.info(" SSL: " + mSettings.isSSL()); mLogger.info(" Host: " + mSettings.getHost()); mLogger.info(" Port: " + mSettings.getPort()); mLogger.info(" LocalOAuth: " + mSettings.isUseALocalOAuth()); mLogger.info(" OriginBlock: " + mSettings.isBlockingOrigin()); } // HMAC? mHmacManager = new HmacManager(this); // ? mWhitelist = new Whitelist(this); // ??? mRequestManager = new DConnectRequestManager(); // ?? mPluginMgr.createDevicePluginList(); showNotification(); } /** * DConnectManager??. */ protected synchronized void stopDConnect() { mRunningFlag = false; mRequestManager.shutdown(); hideNotification(); } /** * ??????. * * ?????? * ????????????????? * * @param request * @param response ? */ private void sendDeliveryProfile(final Intent request, final Intent response) { mDeliveryProfile.onRequest(request, response); } /** * ?ID??. * <br> * * ??????ID?????ID????? * dConnect Manager???IDID?????DNS??????? * * @param event Intent * @param plugin ??? */ private void replaceServiceId(final Intent event, final DevicePlugin plugin) { String serviceId = event.getStringExtra(IntentDConnectMessage.EXTRA_SERVICE_ID); event.putExtra(IntentDConnectMessage.EXTRA_SERVICE_ID, mPluginMgr.appendServiceId(plugin, serviceId)); } /** * ?? */ protected void showNotification() { Intent notificationIntent = new Intent(getApplicationContext(), SettingActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0); NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); builder.setContentIntent(pendingIntent); builder.setTicker(getString(R.string.app_name)); builder.setContentTitle(getString(R.string.app_name)); builder.setContentText(DConnectUtil.getIPAddress(this) + ":" + mSettings.getPort()); int iconType = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? R.drawable.icon : R.drawable.on_icon; builder.setSmallIcon(iconType); startForeground(ONGOING_NOTIFICATION_ID, builder.build()); } /** * ?? */ protected void hideNotification() { stopForeground(true); } /** * ????. * @param plugin ??? * @param serviceId ID * @param event ?? */ private void createClientOfDevicePlugin(final DevicePlugin plugin, final String serviceId, final Intent event) { Intent intent = new Intent(IntentDConnectMessage.ACTION_GET); intent.setComponent(plugin.getComponentName()); intent.putExtra(DConnectMessage.EXTRA_PROFILE, ServiceDiscoveryProfileConstants.PROFILE_NAME); intent.putExtra(DConnectMessage.EXTRA_SERVICE_ID, serviceId); DiscoveryDeviceRequest request = new DiscoveryDeviceRequest(); request.setContext(this); request.setLocalOAuth(mLocalOAuth); request.setUseAccessToken(true); request.setRequireOrigin(true); request.setDestination(plugin); request.setRequest(intent); request.setEvent(event); request.setDevicePluginManager(mPluginMgr); addRequest(request); } /** * ??Intent??. * @param request * @param response ??? * @return ???Intent */ protected Intent createResponseIntent(final Intent request, final Intent response) { int requestCode = request.getIntExtra(IntentDConnectMessage.EXTRA_REQUEST_CODE, ERROR_CODE); ComponentName cn = request.getParcelableExtra(IntentDConnectMessage.EXTRA_RECEIVER); Intent intent = new Intent(response); intent.putExtra(IntentDConnectMessage.EXTRA_REQUEST_CODE, requestCode); intent.putExtra(IntentDConnectMessage.EXTRA_PRODUCT, getString(R.string.app_name)); intent.putExtra(IntentDConnectMessage.EXTRA_VERSION, DConnectUtil.getVersionName(this)); // HMAC? String origin = request.getStringExtra(IntentDConnectMessage.EXTRA_ORIGIN); if (origin == null) { String accessToken = request.getStringExtra(DConnectMessage.EXTRA_ACCESS_TOKEN); if (accessToken != null) { origin = findOrigin(accessToken); } } if (origin != null) { if (mHmacManager.usesHmac(origin)) { String nonce = request.getStringExtra(IntentDConnectMessage.EXTRA_NONCE); if (nonce != null) { String hmac = mHmacManager.generateHmac(origin, nonce); if (hmac != null) { intent.putExtra(IntentDConnectMessage.EXTRA_HMAC, hmac); } } } } else { mLogger.warning("Origin is not found."); } intent.setComponent(cn); return intent; } /** * ???Origin??. * * @param accessToken * @return Origin */ private String findOrigin(final String accessToken) { ClientPackageInfo packageInfo = LocalOAuth2Main.findClientPackageInfoByAccessToken(accessToken); if (packageInfo == null) { return null; } // Origin is a package name of LocalOAuth client. return packageInfo.getPackageInfo().getPackageName(); } /** * ???????????. * * @param request ??? * @return ????????<code>true</code>? * ????????<code>false</code> */ private boolean allowsOrigin(final Intent request) { String originExp = request.getStringExtra(IntentDConnectMessage.EXTRA_ORIGIN); if (originExp == null) { // NOTE: ??????????? // ?????????????. return false; } for (int i = 0; i < IGNORED_ORIGINS.length; i++) { if (originExp.equals(IGNORED_ORIGINS[i])) { return true; } } if (!mSettings.isBlockingOrigin()) { return true; } return mWhitelist.allows(OriginParser.parse(originExp)); } /** * ??????. * @param request ?? * @param response ??? */ public void sendResponse(final Intent request, final Intent response) { sendBroadcast(createResponseIntent(request, response)); } /** * ??. * @param receiver ??BroadcastReceiver * @param event ?? */ public void sendEvent(final String receiver, final Intent event) { if (BuildConfig.DEBUG) { mLogger.info(String.format("sendEvent: %s intent: %s", receiver, event.getExtras())); } Intent targetIntent = new Intent(event); targetIntent.setComponent(ComponentName.unflattenFromString(receiver)); sendBroadcast(targetIntent); } /** * Origin????. */ private enum OriginError { /** * ??. */ NONE, /** * ?????????. */ NOT_SPECIFIED, /** * 2??????????. */ NOT_UNIQUE, /** * ?????????(???????)???. */ NOT_ALLOWED } }