根据提供的示例项目的文档和模拟,我未能成功将HomeConnect SDK Beta v1.2.0(https://developer.home-connect.com)实施到Android应用中。 不幸的是,我无法完成授权流程,并且遇到错误“CLIENT_NOT_INITIALIZED”。我知道SDK仍然处于测试阶段,但也许有人已经有了一些经验,可以帮助我解决这个问题。
出于测试目的并隔离问题,我创建了以下应用程序。 我正在应用程序覆盖中初始化HomeConnect类,在onCreate()中传递我的凭据并设置getter方法:
import android.app.Application;
import com.home_connect.sdk.Environment;
import com.home_connect.sdk.services.HomeConnect;
public class HCSignInDemoApplication extends Application {
private static HomeConnect mHomeConnect;
@Override
public void onCreate() {
super.onCreate();
mHomeConnect = new HomeConnect("signInDemo", this, "<MY_API_KEY>", Environment.SIMULATOR);
}
public static HomeConnect getHomeConnect() {
return mHomeConnect;
}
}
在MainActivity的onCreate()中,如果尚未授权HomeConnect类,则按钮的OnClickListener正在调用AuthorizationDialogFragment:
public class MainActivity extends AppCompatActivity implements AuthorizationDialogFragment.OnAuthorizedCallback {
Button loginButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loginButton = (Button) findViewById(R.id.loginBtn);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainActivity", "LoginButton clicked");
if(HCSignInDemoApplication.getHomeConnect().isAuthorized()){
Log.d("MainActivity", "Is authorized");
} else {
Log.d("MainActivity", "Open HC AuthorizationDialogFragment");
new AuthorizationDialogFragment().show(getSupportFragmentManager(), "");
}
}
});
}
@Override
public void onAuthorized() {
Log.d("MainActivity", "onAuthorized called");
}
}
在按钮上单击以下DialogFragment应该打开一个WebView,用户可以在其中输入他/她的HomeConnect帐户的电子邮件和密码并获取访问令牌。
import com.home_connect.sdk.exceptions.HomeConnectException;
import com.home_connect.sdk.internal.util.TextUtil;
import com.home_connect.sdk.model.Permission;
import com.home_connect.sdk.property.RxBinder;
import com.home_connect.sdk.services.HomeConnect;
import java.util.Set;
import rx.functions.Action1;
/**
* {@link DialogFragment} which is shown at the authorization process
*/
public class AuthorizationDialogFragment extends DialogFragment {
private WebView mWebView;
private OnAuthorizedCallback onAuthorizedCallback;
public interface OnAuthorizedCallback {
void onAuthorized();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.AppTheme);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mWebView = new WebView(inflater.getContext());
return mWebView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mWebView = null;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnAuthorizedCallback) {
onAuthorizedCallback = (OnAuthorizedCallback) context;
}
}
@Override
public void onDetach() {
super.onDetach();
onAuthorizedCallback = null;
}
/**
* set the {@link Permission} scopes and make a Token request via {@link HomeConnect}
*/
@Override
public void onResume() {
super.onResume();
Set<Permission> scopes = Permission.createScope(
Permission.IDENTIFY_APPLIANCE
);
RxBinder.bind(this
, HCSignInDemoApplication.getHomeConnect().requestToken(mWebView, scopes)
, new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(getContext(), "token request successful!", Toast.LENGTH_LONG).show();
if (onAuthorizedCallback != null) {
onAuthorizedCallback.onAuthorized();
}
}
}
, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
if (throwable instanceof HomeConnectException) {
HomeConnectException exception = (HomeConnectException) throwable;
showErrorDialog(exception);
dismiss();
}
}
}
);
}
/**
* Necessary call to unbind the {@link RxBinder} to avoid memory leaks
*/
@Override
public void onPause() {
super.onPause();
RxBinder.unbind(this);
}
protected void showErrorDialog(@Nullable HomeConnectException error) {
Log.i("HCAuthDialogFragment", "Error: " + error);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.error);
if (error != null && error.apiError != null && !TextUtil.isEmpty(error.apiError.description)) {
builder.setMessage(error.apiError.description);
} else if (error != null && !TextUtil.isEmpty(error.description)) {
builder.setMessage(error.description);
} else if (error != null && !TextUtil.isEmpty(error.httpMessage)) {
builder.setMessage(error.httpMessage);
} else {
builder.setMessage(R.string.unknown_error);
}
builder.setPositiveButton(R.string.ok, null);
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
}
好的,所以会发生什么而不是预期的WebView对话框是一个AlertDialog,错误代码为“CLIENT_NOT_INITIALIZED”,描述“客户端尚未初始化。请确保您的API密钥和重定向URL正确”。<登记/> API密钥肯定是正确的,但我不会在任何时候主动传递重定向URL,也没有选项。 完整日志如下所示:
Connected to process 3108 on device Nexus_5_API_24 [emulator-5554]
I/art: Not late-enabling -Xcheck:jni (already on)
W/art: Unexpected CPU variant for X86 using defaults: x86_64
E/art: Failed sending reply to debugger: Broken pipe
I/art: Debugger is no longer active
I/art: Starting a blocking GC Instrumentation
W/System: ClassLoader referenced unknown path: /data/app/android.test.net.hcsignindemo-1/lib/x86_64
I/InstantRun: Instant Run Runtime started. Android package is android.test.net.hcsignindemo, real application class is android.test.net.hcsignindemo.HCSignInDemoApplication.
W/System: ClassLoader referenced unknown path: /data/app/android.test.net.hcsignindemo-1/lib/x86_64
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
W/art: Verification of java.lang.Object android.support.v7.appcompat.R$attr.access$super(android.support.v7.appcompat.R$attr, java.lang.String, java.lang.Object[]) took 708.872ms
I/art: Background sticky concurrent mark sweep GC freed 20350(3MB) AllocSpace objects, 18(348KB) LOS objects, 39% free, 6MB/10MB, paused 5.598ms total 1.051s
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 1
E/EGL_emulation: tid 3224: eglSurfaceAttrib(1146): error 0x3009 (EGL_BAD_MATCH)
W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x72c1e3a56900, error=EGL_BAD_MATCH
I/Choreographer: Skipped 88 frames! The application may be doing too much work on its main thread.
D/MainActivity: LoginButton clicked
D/MainActivity: Open HC AuthorizationDialogFragment
W/System: ClassLoader referenced unknown path: /system/app/webview/lib/x86_64
I/WebViewFactory: Loading com.android.webview version 52.0.2743.100 (code 275610060)
I/cr_LibraryLoader: Time to load native libraries: 59 ms (timestamps 4797-4856)
I/cr_LibraryLoader: Expected native library version number "52.0.2743.100", actual native library version number "52.0.2743.100"
V/WebViewChromiumFactoryProvider: Binding Chromium to main looper Looper (main, tid 1) {a14c6d}
I/cr_LibraryLoader: Expected native library version number "52.0.2743.100", actual native library version number "52.0.2743.100"
I/chromium: [INFO:library_loader_hooks.cc(143)] Chromium logging enabled: level = 0, default verbosity = 0
I/cr_BrowserStartup: Initializing chromium process, singleProcess=true
W/cr_media: Requires BLUETOOTH permission
I/cr_DRP: No DRP key due to exception:java.lang.ClassNotFoundException: com.android.webview.chromium.Drp
W/cr_AwContents: onDetachedFromWindow called when already detached. Ignoring
I/ANDROID.TEST.NET.HCSIGNINDEMO: (InitializeCall.java:40) createRequest: main creating init request...
I/Choreographer: Skipped 108 frames! The application may be doing too much work on its main thread.
E/EGL_emulation: tid 3224: eglSurfaceAttrib(1146): error 0x3009 (EGL_BAD_MATCH)
W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x72c1e3a56c00, error=EGL_BAD_MATCH
I/Choreographer: Skipped 34 frames! The application may be doing too much work on its main thread.
W/art: Attempt to remove non-JNI local reference, dumping thread
I/art: Background partial concurrent mark sweep GC freed 14247(1514KB) AllocSpace objects, 6(120KB) LOS objects, 21% free, 14MB/18MB, paused 2.204ms total 127.182ms
I/ANDROID.TEST.NET.HCSIGNINDEMO: (InitializeCall.java:57) onJsonServerResponse: RxCachedThreadScheduler-1 handling init...
I/ANDROID.TEST.NET.HCSIGNINDEMO: (RequestSimulatorAuthorizationCall.java:36) createRequest: RxCachedThreadScheduler-1 creating simulator token request...
I/ANDROID.TEST.NET.HCSIGNINDEMO: (HttpApiCallClient.java:197) addInitializationInfo: RxCachedThreadScheduler-2 adding init info...
I/ANDROID.TEST.NET.HCSIGNINDEMO: (HCUtils.java:79) call: main retry attempt
I/HCAuthDialogFragment: Error: com.home_connect.sdk.exceptions.HomeConnectException: Description: The client hasn't been initialized. Please make sure your API key and redirect URL are correct
Error code: CLIENT_NOT_INITIALIZED
E/EGL_emulation: tid 3224: eglSurfaceAttrib(1146): error 0x3009 (EGL_BAD_MATCH)
W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x72c1e3a56c00, error=EGL_BAD_MATCH
Application terminated.
HomeConnect SDK文档有一个故障排除部分,但遗憾的是不包括发生的错误。 我错过了一些必要的东西,或者可能是,当前版本的SDK还不能运行吗?
有什么用? HomeConnect提供了一个带有swagger UI(https://apiclient.home-connect.com/)的API客户端。在Chrome开发工具和Postman的帮助下,我能够找到client_secret(我在文档中找不到任何地方)。 有了这个,就可以通过HttpURLconnection获得一个有效的访问令牌,它允许使用一组示例设备获得JSON响应。但这忽略了使用SDK的重点。
//!!!!!! TEST !!!!!!
//get access_token through client_secret
private class PostRequestForToken extends AsyncTask<String, Void, HCAccessTokenModel> {
@Override
protected HCAccessTokenModel doInBackground(String... params) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try{
// Open connection and handle request (OutputStream)
URL url = new URL(params[0]); // ParamsArray[0] is url address, needs to be "https://developer.home-connect.com/security/oauth/token"
connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setDoInput(true);
connection.setDoOutput(true);
Uri.Builder builder = new Uri.Builder()
.appendQueryParameter("client_id", "<MY_API_KEY>") // API key from https://developer.home-connect.com/?q=user/my_apps
.appendQueryParameter("code", "96b5673f5e56eb9efba9424f94d5c0958377185767d6f2711f53d72c7ba4bb7f") // redirect from https://developer.home-connect.com/security/oauth/authorize?client_id=<YOUR API KEY HERE>&redirect_uri=https://apiclient.home-connect.com/o2c.html&response_type=code&scope=IdentifyAppliance
.appendQueryParameter("grant_type", "authorization_code")
.appendQueryParameter("redirect_uri", "https://apiclient.home-connect.com/o2c.html") // redirect URL set in https://developer.home-connect.com/?q=user/my_apps
.appendQueryParameter("client_secret", "C8DED1367FEEFDE94C1390B6B3EC3EE9397A78943A6BA18CB416A430A0DE696F"); // retrieved through URL log on Swagger UI
String query = builder.build().getEncodedQuery();
OutputStream os = connection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(query);
writer.flush();
writer.close();
os.close();
connection.connect();
// Handle response (InputStream)
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null){
buffer.append(line);
}
String finalJson = buffer.toString();
// Parse to JSON Object
JSONObject parentObject = new JSONObject(finalJson);
HCAccessTokenModel tokenModel = new HCAccessTokenModel();
tokenModel.setAccess_token(parentObject.getString("access_token"));
tokenModel.setExpires_in(parentObject.getString("expires_in"));
tokenModel.setId_token(parentObject.getString("id_token"));
tokenModel.setRefresh_token(parentObject.getString("refresh_token"));
tokenModel.setScope(parentObject.getString("scope"));
tokenModel.setToken_type(parentObject.getString("token_type"));
return tokenModel;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
} try {
if (reader != null){
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(HCAccessTokenModel tokenModel) {
super.onPostExecute(tokenModel);
Log.d(TAG, "token_type: " + tokenModel.getToken_type() + "\naccess_token: " + tokenModel.getAccess_token());
new GetRequestForApplianceList().execute("https://developer.home-connect.com/api/homeappliances", tokenModel.getToken_type(), tokenModel.getAccess_token());
}
}
private class GetRequestForApplianceList extends AsyncTask<String, Void, List<HCApplianceModel>> {
@Override
protected List<HCApplianceModel> doInBackground(String... params) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
// Open connection
URL url = new URL(params[0]); // ParamsArray[0] is url address, needs to be "https://developer.home-connect.com/api/homeappliances"
String token_type = params[1]; // ParamsArray[1] is token type, needs to be "Bearer"
String access_token = params[2]; // ParamsArray[2]
connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(10000);
connection.setConnectTimeout(15000);
connection.setRequestProperty("Authorization", token_type + " " + access_token);
connection.setRequestProperty("Content-Type", "application/vnd.bsh.sdk.v1+json");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 ( compatible ) ");
connection.setRequestProperty("Accept", "*/*");
//connection.connect();
Log.d(TAG, "Connected");
// Handle response (InputStream)
InputStream stream = new BufferedInputStream(connection.getInputStream());
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
String finalJson = buffer.toString();
// Parse to JSON Array
JSONObject parentObject = new JSONObject(finalJson);
JSONArray parentArray = parentObject.getJSONObject("data").getJSONArray("homeappliances"); //"homeappliances" is key of JSONArray
List<HCApplianceModel> applianceModelList = new ArrayList<>();
for (int i = 0; i < parentArray.length(); i++) {
JSONObject finalObject = parentArray.getJSONObject(i);
HCApplianceModel applianceModel = new HCApplianceModel();
applianceModel.setHaId(finalObject.getString("haId"));
applianceModel.setVib(finalObject.getString("vib"));
applianceModel.setBrand(finalObject.getString("brand"));
applianceModel.setType(finalObject.getString("type"));
applianceModel.setName(finalObject.getString("name"));
applianceModel.setEnumber(finalObject.getString("enumber"));
applianceModel.setConnected(finalObject.getBoolean("connected"));
applianceModelList.add(applianceModel);
}
return applianceModelList; // This is result passed to onPostExecute(List<HCApplianceModel> applianceModels)
} catch (ProtocolException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(List<HCApplianceModel> applianceModels) {
super.onPostExecute(applianceModels);
// Set TextView for testing
String message = "";
for (int j = 0; j < applianceModels.size(); j++){
message += "haId: " + applianceModels.get(j).getHaId() + "\n";
message += "vib: " + applianceModels.get(j).getVib() + "\n";
message += "brand: " + applianceModels.get(j).getBrand() + "\n";
message += "type: " + applianceModels.get(j).getType() + "\n";
message += "name: " + applianceModels.get(j).getName() + "\n";
message += "enumber: " + applianceModels.get(j).getEnumber() + "\n";
message += "connected: " + applianceModels.get(j).isConnected() + "\n" + "\n";
}
textView.setText(message);
}
}