View attachment Featured image.jpg
As the start of the new academic year approaches, many college students will be leaving their parents to start college life. However, a lack of experience makes it easy for college students to become victims of electronic fraud such as phone scams.
The start of the new academic year is often a period of time that sees an uptick in phone scams, especially those targeting college students. Some scammers trick students to download and register an account on malicious financial apps embedded with viruses and Trojan horses or ones that imitate legitimate apps. With such malicious apps installed on students' phones, scammers are able to steal students' sensitive data, such as bank card numbers and passwords. Some scammers trick students, by offering them small gifts or coupons, to scan QR codes which then direct them to pages that ask users to enter their personal information, such as their phone number and address. Once a student has done this, they will receive a large number of fraudulent calls and junk SMS messages from then on. If the students scan QR codes linking to phishing websites, their personal data may be leaked and sold for malicious purposes. Some scammers even lie about offering students scholarships or grants, in order to trick them into visiting phishing websites and entering their bank account numbers and passwords, causing significant financial losses to such students.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
To deal with the ever-changing tricks of fraudsters, an app needs to detect phishing websites, malicious apps, and other risks and remind users to be on the lookout for such risks with in-app tips, in order to keep users and their data safe. So, is there a one-stop service that can enhance app security from multiple dimensions? Fortunately, HMS Core Safety Detect can help developers quickly build security capabilities into their apps, and help vulnerable user groups such as college students safeguard their information and property.
The AppsCheck API in Safety Detect allows your app to obtain a list of malicious apps installed on a user's device. The API can identify 99% of malicious apps and detect unknown threats based on app behavior. Your app can then use this information to determine whether to restrict users from performing in-app payments and other sensitive operations.
The URLCheck API in Safety Detect checks whether an in-app URL is malicious. If the URL is determined to be malicious, the app can warn the user of the risk or block the URL.
Safety Detect also provides capabilities to check system integrity and detect fake users, helping developers quickly improve their app security. The integration process is straightforward, which I'll describe below.
Demo
Integration ProcedurePreparationsYou can follow the instructions here to prepare for the integration.
Using the AppsCheck APIYou can directly call getMaliciousAppsList of SafetyDetectClient to obtain a list of malicious apps. The sample code is as follows:
Code:
private void invokeGetMaliciousApps() {
SafetyDetectClient appsCheckClient = SafetyDetect.getClient(MainActivity.this);
Task task = appsCheckClient.getMaliciousAppsList();
task.addOnSuccessListener(new OnSuccessListener<MaliciousAppsListResp>() {
@Override
public void onSuccess(MaliciousAppsListResp maliciousAppsListResp) {
// Indicates that communication with the service was successful.
// Use resp.getMaliciousApps() to obtain a list of malicious apps.
List<MaliciousAppsData> appsDataList = maliciousAppsListResp.getMaliciousAppsList();
// Indicates that the list of malicious apps was successfully obtained.
if(maliciousAppsListResp.getRtnCode() == CommonCode.OK) {
if (appsDataList.isEmpty()) {
// Indicates that no known malicious apps were detected.
Log.i(TAG, "There are no known potentially malicious apps installed.");
} else {
Log.i(TAG, "Potentially malicious apps are installed!");
for (MaliciousAppsData maliciousApp : appsDataList) {
Log.i(TAG, "Information about a malicious app:");
// Use getApkPackageName() to obtain the APK name of the malicious app.
Log.i(TAG, "APK: " + maliciousApp.getApkPackageName());
// Use getApkSha256() to obtain the APK SHA-256 of the malicious app.
Log.i(TAG, "SHA-256: " + maliciousApp.getApkSha256());
// Use getApkCategory() to obtain the category of the malicious app.
// Categories are defined in AppsCheckConstants.
Log.i(TAG, "Category: " + maliciousApp.getApkCategory());
}
}
}else{
Log.e(TAG,"getMaliciousAppsList failed: "+maliciousAppsListResp.getErrorReason());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
// An error occurred during communication with the service.
if (e instanceof ApiException) {
// An error with the HMS API contains some
// additional details.
ApiException apiException = (ApiException) e;
// You can retrieve the status code using the apiException.getStatusCode() method.
Log.e(TAG, "Error: " + SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getStatusMessage());
} else {
// A different, unknown type of error occurred.
Log.e(TAG, "ERROR: " + e.getMessage());
}
}
});
}
Using the URLCheck API1. Initialize the URLCheck API.
Before using the URLCheck API, you must call the initUrlCheck method to initialize the API. The sample code is as follows:
Code:
SafetyDetectClient client = SafetyDetect.getClient(getActivity());
client.initUrlCheck();
2. Request a URL check.
You can pass target threat types to the URLCheck API as parameters. The constants in the UrlCheckThreat class include the current supported threat types.
Code:
public class UrlCheckThreat {
// URLs of this type are marked as URLs of pages containing potentially malicious apps (such as home page tampering URLs, Trojan-infected URLs, and malicious app download URLs).
public static final int MALWARE = 1;
// URLs of this type are marked as phishing and spoofing URLs.
public static final int PHISHING = 3;
}
a. Initiate a URL check request.
The URL to be checked contains the protocol, host, and path but does not contain the query parameter. The sample code is as follows:
Code:
String url = "https://developer.huawei.com/consumer/cn/";
SafetyDetect.getClient(this).urlCheck(url, appId, UrlCheckThreat.MALWARE, UrlCheckThreat.PHISHING).addOnSuccessListener(this, new OnSuccessListener<UrlCheckResponse >(){
@Override
public void onSuccess(UrlCheckResponse urlResponse) {
if (urlResponse.getUrlCheckResponse().isEmpty()) {
// No threat exists.
} else {
// Threats exist.
}
}
}).addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// An error occurred during communication with the service.
if (e instanceof ApiException) {
// HMS Core (APK) error code and corresponding error description.
ApiException apiException = (ApiException) e;
Log.d(TAG, "Error: " + CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()));
// Note: If the status code is SafetyDetectStatusCode.CHECK_WITHOUT_INIT,
// you did not call the initUrlCheck() method or you have initiated a URL check request before the call is completed.
// If an internal error occurs during the initialization, you need to call the initUrlCheck() method again to initialize the API.
} else {
// An unknown exception occurred.
Log.d(TAG, "Error: " + e.getMessage());
}
}
});
b. Call the getUrlCheckResponse method of the returned UrlCheckResponse object to obtain the URL check result.
The result contains List<UrlCheckThreat>, which includes the detected URL threat type. If the list is empty, no threat is detected. Otherwise, you can call getUrlCheckResult in UrlCheckThreat to obtain the specific threat code. The sample code is as follows:
Code:
final EditText testRes = getActivity().findViewById(R.id.fg_call_urlResult);
List<UrlCheckThreat> list = urlCheckResponse.getUrlCheckResponse();
if (list.isEmpty()) {
testRes.setText("ok");
}
else{
for (UrlCheckThreat threat : list) {
int type = threat.getUrlCheckResult();
}
}
3. Close the URL check session.
If your app does not need to call the URLCheck API anymore or will not need to for a while, you can call the shutdownUrlCheck method to close the URL check session and release relevant resources.
Code:
SafetyDetect.getClient(this).shutdownUrlCheck();
ConclusionElectronic fraud such as phone scams are constantly evolving and becoming more and more difficult to prevent, bringing great challenges to both developers and users. To combat such risks, developers must utilize technical means to identify phishing websites, malicious apps, and other risks, in order to safeguard users' personal information and property.
In this article, I demonstrated how HMS Core Safety Detect can be used to effectively combat electronic fraud. The whole integration process is straightforward and cost-efficient, and is a quick and effective way to build comprehensive security capabilities into an app.
Related
Background
Most social networking apps offer a set of standard features, such as accurate connections with nearby people, face-to-face grouping, and short-distance local chatting in point-to-point mode. However, while the common "Nearby people" feature can help users discover others hundreds of meters, or even several kilometers away from them, it is not well suited to connect users who are in each other's presence. Likewise, for "face-to-face grouping", it would be great to connect with group members in your vicinity, and communicate with friends, even when there is no Internet connection. Thanks to Nearby Service, this long sought-after ideal is now a full-blown reality! So, without further ado, let's take a look at how you can integrate Nearby Service, to create the social networking app of your dreams.
Applicable Scenarios
Here's an example of an app that has integrated Nearby Service. With Nearby Service, the app is able to discover nearby people, and implement a myriad of different functions, such as face-to-face grouping, group chats, private chats, and chat log migration.
Nearby people
Nearby Service ensures that users can discover people within several meters, with a high degree of accuracy. The service enables users to connect with people in their vicinity, so long as they are in the same room, such as at a conference, exhibition, bar, or cafe.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Face-to-face grouping
With Nearby Service, users are able to directly create a group on their device, and set a precise location range. Only people within this location range will be eligible to join the group.
Free chat
Nearby Service facilitates short-distance group chatting, and does not require the group members to add each other. Instead, members can join the group automatically when they are within the location range, and will automatically exit the group when they leave this range.
Private chat
Nearby Service implements short-distance local chatting in point-to-point mode, enabling users to communicate with just the two devices in question, and free of any concern regarding data leakage, due to the presence of encrypted communications data that is not uploaded to the cloud. Furthermore, the feature supports communications in areas with poor signal coverage, such as on high-speed rail or the metro.
Chat log migration
Nearby Service offers a powerful file transmission function as well. The communications APIs in Nearby Service ensure that users enjoy high-speed, traffic-free data transmission when they need to migrate chat records on multiple devices.
Preparing Tools
Two or more Android phones
Android Studio (3.X or later)
Creating an App
Import the source code to Android Studio.
Register as a Huawei developer.
Create an app in Huawei AppGallery. For details, please refer to HUAWEI Developers-App Development. Note that you will need to download the agconnect-services.json file, generate the signing certificate, and save them to the app directory.
Run the adb command to install the generated APK file on the test phones.
Core Code
For more information about the APIs used in the app, please visit Nearby Service-Development Guides.
This app is created in MVP mode. For the source code, you can find the corresponding class in the code path: com\huawei\hms\nearby\im.
1.Nearby people
As shown in the following code, you will need to pass the view object during the initialization of the NearbyPeoplePresenter class. When the NearbyPeoplePresenter.findNearbyPeople() API is called to discover nearby people, call the view API to perform subsequent steps.
Code:
public NearbyPeoplePresenter(Context mContext, INearbyPeopleView view) {
super(mContext, view);
nearbyAgent = new NearbyAgent(mContext, new NearbyAgent.INearbyMessageView() {
@Override
public void onMessageFound(MessageBean messageBean) {
// notify view when found someone
view.onMemberChanged(false,messageBean);
}
@Override
public void onMessageLost(MessageBean messageBean) {
view.onMemberChanged(true,messageBean);
}
@Override
public void onMsgSendResult(boolean isSucceed, MessageBean item) {
view.onLoginResult(isSucceed,item);
if (!isSucceed) {
handler.postDelayed(() -> findNearbyPeople(),DURATION_RE_LOGIN);
}
}
});
handler = new Handler(Looper.getMainLooper());
}
public void findNearbyPeople() {
nearbyAgent.broadcastMessage(null,MessageBean.ACTION_TAG_ONLINE);
nearbyAgent.startScan();
}
2.Face-to-face grouping
This process is similar to that for nearby people. Pass the view object during the initialization of the CreateGroupPresenter.java class. When the joinGroup(groupId) API is called, the user is added to the group. The result is returned by calling the view API.
INearbyPeopleView and xxxView in the following steps are a group of APIs. Their implementation is the corresponding Activity, which you can view through the code path: com\huawei\hms\nearby\im\ui\adapter.
Code:
public CreateGroupPresenter(Context mContext, ICreateGroupView view) {
super(mContext, view);
nearbyAgent = new NearbyAgent(mContext, new NearbyAgent.INearbyMessageView() {
@Override
public void onMessageFound(MessageBean messageBean) {
view.onPeopleFound(messageBean);
}
@Override
public void onMessageLost(MessageBean messageBean) {}
@Override
public void onMsgSendResult(boolean isSucceed, MessageBean item) {
view.onJoinGroupResult(isSucceed,item);
}
});
}
public void joinGroup(String groupId) {
nearbyAgent.broadcastMessage(groupId,"join group");
nearbyAgent.startScan(groupId);
}
3.Free chat
Pass the view object during the initialization of the GroupChatPresenter.java class. You can call broadcastMessage(groupId, sendContent) to send a message to a specified group. The group is not limited if groupId is null. Also, the findMessage(groupId) API can be called to find the message of a specified group. After the message is found, the view API will be called to return the message.
Code:
public GroupChatPresenter(Context mContext, IGroupChatView view) {
super(mContext, view);
nearbyAgent = new NearbyAgent(mContext, new NearbyAgent.INearbyMessageView() {
@Override
public void onMessageFound(MessageBean messageBean) {
view.onMessageFound(messageBean);
}
@Override
public void onMessageLost(MessageBean messageBean) {
}
@Override
public void onMsgSendResult(boolean isSucceed, MessageBean item) {
view.onMsgSendResult(isSucceed,item);
}
});
}
public void broadcastMessage(String groupId, String sendContent) {
nearbyAgent.broadcastMessage(groupId,sendContent);
}
public void findMessage(String groupId) {
nearbyAgent.startScan(groupId);
}
4.Private chat
Private chat implementation differs from the process above. The NearbyConnectionPresenter.java class provides the following APIs:
findNearbyPeople(): Discovering people nearby.
requestConnect(): Connecting users with each other.
sendMessage(String msgStr): Sending messages of strings.
sendFile(Uri uri): Sending files.
Code:
/**
* scanAndBroadcasting to find nearby people
*/
public void findNearbyPeople(){
mDiscoveryEngine.startScan(serviceId, new ScanEndpointCallback() {
@Override
public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) {
Log.d(TAG, "onFound -- Nearby Connection Demo app: onFound endpoint: " + endpointId);
view.onFound(endpointId,discoveryEndpointInfo);
}
@Override
public void onLost(String endpointId) {
Log.d(TAG, "onLost -- Nearby Connection Demo app: Lost endpoint: " + endpointId);
view.onLost(endpointId);
}
}, scanOption);
}
/**
* request to connect with remote device
* @param endpointId the endpointId of remote device
*/
public void requestConnect(String endpointId) {
Log.d(TAG, "requestConnect -- endpoint: " + endpointId);
mDiscoveryEngine.requestConnect(myNameStr, endpointId, connectCallback);
}
/**
* Send message ,Data.Type.BYTES
*/
public MessageBean sendMessage(String msgStr) {
MessageBean item = new MessageBean();
item.setUserName(CommonUtil.userName);
item.setMsg(msgStr);
item.setType(MessageBean.TYPE_SEND_TEXT);
item.setSendTime(DateUtils.getCurrentTime(DateUtils.FORMAT));
Data data = Data.fromBytes(gson.toJson(item).getBytes(Charset.defaultCharset()));
mTransferEngine.sendData(mEndpointId, data);
return item;
}
/**
* send file ,Data.Type.FILE
* @param uri
*/
public Data sendFile(Uri uri) {
Data filePayload;
try {
ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(uri, "r");
filePayload = Data.fromFile(pfd);
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "File not found, cause: ", e);
return null;
}
String fileName = FileUtil.getFileRealNameFromUri(mContext, uri);
String filenameMessage = filePayload.getId() + ":" + fileName;
Data filenameBytesPayload = Data.fromBytes(filenameMessage.getBytes(StandardCharsets.UTF_8));
mTransferEngine.sendData(mEndpointId, filenameBytesPayload);
mTransferEngine.sendData(mEndpointId, filePayload);
return filePayload;
}
Learn More
For more detailed information, please visit HUAWEI Developers.
For further instructions, please visit Development Guides.
You can join the HMS Core developer discussion by going to Reddit.
You can download the demo and sample code on GitHub.
To resolve any integration issues, please go to Stack Overflow.
What Does a Delivery Failure Mean?
A delivery failure refers to a situation where a user does not receive the item they have purchased.
A user purchases a virtual product or service within an app, but the user does not receive the purchased item due to an error (such as network exception or process termination) occurring in data synchronization between the app and the in-app purchases server.
Delivery failures can be damaging to both users and developers, and may lead to your app receiving a one-star rating, or users directly turning away from it. This may in turn have a knock-on effect on potential users, driving them away from your app before even giving it a chance. Clearly, this is something that needs to be avoided.
Common Way of Handling a Delivery Failure
The user claims that they have paid but did not receive the purchased item, and subsequently requests a refund. This will undoubtedly break the user's trust in your app.
Integrating HUAWEI IAP to Eliminate Delivery Failures
With HUAWEI IAP, you can completely eliminate the chance of a missing delivery occurring.
Your app calls the APIs of HUAWEI IAP to query order information from the HUAWEI IAP server. The server will return orders that are paid but not consumed to your app, which will then redeliver the products based on the query result, and tell the server to update the order status.
You can complete integration by following these instructions:
HUAWEI IAP: Purchase Process
For a purchased consumable, your app will call the consumption API to consume the product. If the API call fails, product delivery will fail. A non-consumable product or subscription service will not experience a delivery failure.
A typical consumable purchasing process:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
1. A user initiates a purchase request from your app, which your app then sends to HMS Core (APK).
2. Request a delivery. Verify the signature of purchase data before delivering the requested product. 3. Deliver the product and send the purchase token to your app server. This token can be used to obtain the product delivery status so that you can determine whether to redeliver a product if its consumption fails due to delivery failure.
3. After a product is successfully delivered, call the consumeOwnedPurchaseAPI to consume the product and send a notification to the Huawei IAP server to update its delivery status. purchaseToken is passed in the API call request, and once consumption is complete, the Huawei IAP server resets the product status to available for purchase. The product can then be purchased again.
Besides the consumeOwnedPurchase API provided by the IAP client, your app can also use the API provided by the IAP server for product consumption. For details, please refer to Confirming the Purchase for the Order Service.
How HUAWEI IAP redelivers a product:
With HUAWEI IAP you can redeliver a consumable when a delivery failure occurs because of data synchronization errors (caused by network exception, process termination, or others) between your app and the IAP server. The following figure illustrates the redelivery process.
Your app needs to trigger a redelivery in the following scenarios:
The app launches.
Result code -1 (OrderStatusCode. ORDER_STATE_FAILED) is returned for a purchase request.
Result code 60051 (OrderStatusCode. ORDER_PRODUCT_OWNED) is returned for a purchase request.
Implement the redelivery function as follows:
1. Use obtainOwnedPurchases to obtain the purchase information about the purchased but undelivered consumable. Specify priceType as 0 in OwnedPurchasesReq.
If this API is successfully called, HUAWEI IAP will return an OwnedPurchasesResult object, which contains the purchase data and signature data of all purchased products that have not being delivered. Use the public key allocated by AppGallery Connect to verify the signature. For more information about the verification method, please refer to Verifying the Signature in the Returned Result.
Each purchase’s data is a character string in JSON format that contains the parameters listed in InAppPurchaseData. You need to parse the purchaseState field from the InAppPurchaseData character string. If purchaseState of a purchase is 0, the purchase is successful and delivery will be performed.
Code:
// Construct an OwnedPurchasesReq object.
OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq();
// priceType: 0: consumable; 1: non-consumable; 2: subscription
ownedPurchasesReq.setPriceType(0);
// Obtain the Activity object that calls the API.
final Activity activity = getActivity();
// Call the obtainOwnedPurchases API to obtain the order information about all consumable products that have been purchased but not delivered.
Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq);
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
@Override
public void onSuccess(OwnedPurchasesResult result) {
// Obtain the execution result if the request is successful.
if (result != null && result.getInAppPurchaseDataList() != null) {
for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) {
String inAppPurchaseData = result.getInAppPurchaseDataList().get(i);
String inAppSignature = result.getInAppSignature().get(i);
// Use the IAP public key to verify the signature of inAppPurchaseData.
// Check the purchase status of each product if the verification is successful. When the payment has been made, deliver the required product. After a successful delivery, consume the product.
try {
InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
int purchaseState = inAppPurchaseDataBean.getPurchaseState();
} catch (JSONException e) {
}
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException) e;
Status status = apiException.getStatus();
int returnCode = apiException.getStatusCode();
} else {
// Other external errors.
}
}
});
2. Call the consumeOwnedPurchase API to consume a delivered product.
Perform a delivery confirmation for all products queried through the obtainOwnedPurchases API. If a product is already delivered, call the consumeOwnedPurchase API to consume the product and instruct the Huawei IAP server to update the delivery status. Following consumption, the Huawei IAP server resets the product status to available for purchase, allowing the product to be purchased again.
For more details, please click:
HUAWEI IAP official website
HUAWEI IAP Development Guide
HUAWEI HMS Core Community
To learn more, please visit:
HUAWEI Developers official website
Development Guide
Reddit to join developer discussions
GitHub or Gitee to download the demo and sample code
Stack Overflow to solve integration problems
Follow our official account for the latest HMS Core-related news and updates.
Original Source
Conventional pop-up ads and roll ads in apps not only frustrate users, but are a headache for advertisers. This is because on the one hand, advertising is expensive, but on the other hand, these ads do not necessarily reach their target audience. The emergence of personalized ads has proved a game changer.
To ensure ads are actually sent to their intended audience, publishers usually need to collect the personal data of users to determine their characteristics, hobbies, recent requirements, and more, and then push targeted ads in apps. Some users are unwilling to share privacy data to receive personalized ads. Therefore, if an app needs to collect, use, and share users' personal data for the purpose of personalized ads, valid consent from users must be obtained first.
HUAWEI Ads provides the capability of obtaining user consent. In countries/regions with strict privacy requirements, it is recommended that publishers access the personalized ad service through the HUAWEI Ads SDK and share personal data that has been collected and processed with HUAWEI Ads. HUAWEI Ads reserves the right to monitor the privacy and data compliance of publishers. By default, personalized ads are returned for ad requests to HUAWEI Ads, and the ads are filtered based on the user's previously collected data. HUAWEI Ads also supports ad request settings for non-personalized ads. For details, please refer to "Personalized Ads and Non-personalized Ads" in the HUAWEI Ads Privacy and Data Security Policies.
To obtain user consent, you can use the Consent SDK provided by HUAWEI Ads or the CMP that complies with IAB TCF v2.0. For details, see Integration with IAB TCF v2.0.
Let's see how the Consent SDK can be used to request user consent and how to request ads accordingly.
Development ProcedureTo begin with, you will need to integrate the HMS Core SDK and HUAWEI Ads SDK. For details, see the development guide.
Using the Consent SDK
1. Integrate the Consent SDK.
a. Configure the Maven repository address.
The code library configuration of Android Studio is different in versions earlier than Gradle 7.0, Gradle 7.0, and Gradle 7.1 and later versions. Select the corresponding configuration procedure based on your Gradle plugin version.
b. Add build dependencies to the app-level build.gradle file.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Replace {version} with the actual version number. For details about the version number, please refer to the version updates. The sample code is as follows:
Code:
dependencies {
implementation 'com.huawei.hms:ads-consent:3.4.54.300'
}
After completing all the preceding configurations, click
on the toolbar to synchronize the build.gradle file and download the dependencies.
2. Update the user consent status.
When using the Consent SDK, ensure that the Consent SDK obtains the latest information about the ad technology providers of HUAWEI Ads. If the list of ad technology providers changes after the user consent is obtained, the Consent SDK will automatically set the user consent status to UNKNOWN. This means that every time the app is launched, you should call the requestConsentUpdate() method to determine the user consent status. The sample code is as follows:
Code:
...
import com.huawei.hms.ads.consent.*;
...
public class ConsentActivity extends BaseActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Check the user consent status.
checkConsentStatus();
...
}
...
private void checkConsentStatus() {
...
Consent consentInfo = Consent.getInstance(this);
...
consentInfo.requestConsentUpdate(new ConsentUpdateListener() {
@Override
public void onSuccess(ConsentStatus consentStatus, boolean isNeedConsent, List<AdProvider> adProviders) {
// User consent status successfully updated.
...
}
@Override
public void onFail(String errorDescription) {
// Failed to update user consent status.
...
}
});
...
}
...
}
If the user consent status is successfully updated, the onSuccess() method of ConsentUpdateListener provides the updated ConsentStatus (specifies the consent status), isNeedConsent (specifies whether consent is required), and adProviders (specifies the list of ad technology providers).
3. Obtain user consent.
You need to obtain the consent (for example, in a dialog box) of a user and display a complete list of ad technology providers. The following example shows how to obtain user consent in a dialog box:
a. Collect consent in a dialog box.
The sample code is as follows:
Code:
...
import com.huawei.hms.ads.consent.*;
...
public class ConsentActivity extends BaseActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Check the user consent status.
checkConsentStatus();
...
}
...
private void checkConsentStatus() {
...
Consent consentInfo = Consent.getInstance(this);
...
consentInfo.requestConsentUpdate(new ConsentUpdateListener() {
@Override
public void onSuccess(ConsentStatus consentStatus, boolean isNeedConsent, List<AdProvider> adProviders) {
...
// The parameter indicating whether the consent is required is returned.
if (isNeedConsent) {
// If ConsentStatus is set to UNKNOWN, ask for user consent again.
if (consentStatus == ConsentStatus.UNKNOWN) {
...
showConsentDialog();
}
// If ConsentStatus is set to PERSONALIZED or NON_PERSONALIZED, no dialog box is displayed to ask for user consent.
else {
...
}
} else {
...
}
}
@Override
public void onFail(String errorDescription) {
...
}
});
...
}
...
private void showConsentDialog() {
// Start to process the consent dialog box.
ConsentDialog dialog = new ConsentDialog(this, mAdProviders);
dialog.setCallback(this);
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
}
Sample dialog box
Note: This image is for reference only. Design the UI based on the privacy page.
More information will be displayed if users tap here.
Note: This image is for reference only. Design the UI based on the privacy page.
b. Display the list of ad technology providers.
Display the names of ad technology providers to the user and allow the user to access the privacy policies of the ad technology providers.
After a user taps here on the information screen, the list of ad technology providers should appear in a dialog box, as shown in the following figure.
Note: This image is for reference only. Design the UI based on the privacy page.
c. Set consent status.
After obtaining the user's consent, use the setConsentStatus() method to set their content status. The sample code is as follows:
Code:
Consent.getInstance(getApplicationContext()).setConsentStatus(ConsentStatus.PERSONALIZED);
d. Set the tag indicating whether a user is under the age of consent.
If you want to request ads for users under the age of consent, call setUnderAgeOfPromise to set the tag for such users before calling requestConsentUpdate().
Code:
// Set the tag indicating whether a user is under the age of consent.
Consent.getInstance(getApplicationContext()).setUnderAgeOfPromise(true);
If setUnderAgeOfPromise is set to true, the onFail (String errorDescription) method is called back each time requestConsentUpdate() is called, and the errorDescription parameter is provided. In this case, do not display the dialog box for obtaining consent. The value false indicates that a user has reached the age of consent.
4. Load ads according to user consent.
By default, the setNonPersonalizedAd method is not called for requesting ads. In this case, personalized and non-personalized ads are requested, so if a user has not selected a consent option, only non-personalized ads can be requested.
The parameter of the setNonPersonalizedAd method can be set to the following values:
ALLOW_ALL: personalized and non-personalized ads.
ALLOW_NON_PERSONALIZED: non-personalized ads.
The sample code is as follows:
Code:
// Set the parameter in setNonPersonalizedAd to ALLOW_NON_PERSONALIZED to request only non-personalized ads.
RequestOptions requestOptions = HwAds.getRequestOptions();
requestOptions = requestOptions.toBuilder().setNonPersonalizedAd(ALLOW_NON_PERSONALIZED).build();
HwAds.setRequestOptions(requestOptions);
AdParam adParam = new AdParam.Builder().build();
adView.loadAd(adParam);
Testing the Consent SDK
To simplify app testing, the Consent SDK provides debug options that you can set.
1. Call getTestDeviceId() to obtain the ID of your device.
The sample code is as follows:
Code:
String testDeviceId = Consent.getInstance(getApplicationContext()).getTestDeviceId();
2. Use the obtained device ID to add your device as a test device to the trustlist.
The sample code is as follows:
Code:
Consent.getInstance(getApplicationContext()).addTestDeviceId(testDeviceId);
3. Call setDebugNeedConsent to set whether consent is required.
The sample code is as follows:
Code:
// Require consent for debugging. In this case, the value of isNeedConsent returned by the ConsentUpdateListener method is true.
Consent.getInstance(getApplicationContext()).setDebugNeedConsent(DebugNeedConsent.DEBUG_NEED_CONSENT);
// Not to require consent for debugging. In this case, the value of isNeedConsent returned by the ConsentUpdateListener method is false.
Consent.getInstance(getApplicationContext()).setDebugNeedConsent(DebugNeedConsent.DEBUG_NOT_NEED_CONSENT);
After these steps are complete, the value of isNeedConsent will be returned based on your debug status when calls are made to update the consent status.
For more information about the Consent SDK, please refer to the sample code.
References
Ads Kit
Development Guide of Ads Kit
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Several months ago, Sony rolled out their all-new PlayStation Plus service, which is home to a wealth of popular classic games. Its official blog wrote that its games catalog "will continue to refresh and evolve over time, so there is always something new to play."
I was totally on board with the idea and so… I thought why not build a lightweight mobile game together with my friends and launch it on a niche app store as a pilot. I did just this. The multiplayer survival game draws on a dark cartoon style and users need to utilize their strategic skills to survive. The game launch was all about sharing ideas, among English users specifically, but it attracted many players from non-English speaking countries like China and Germany. What a surprise!
Like many other game developers, I tried to achieve monetization through in-app user purchases. The app offers many in-game props, such as fancy clothes and accessories, weapons, and skill cards, to deliver a more immersive experience or to help users survive. This posed a significant challenge — as users are based in a number of different countries or regions, the app needs to show product information in the language of the country or region where the user's account is located, as well as the currency. How to do this?
Below is a walkthrough of how I implemented the language and currency localization function and the product purchase function for my app. I turned to HMS Core In-App Purchases (IAP) because it is very accessible. I hope this will help you.
Development ProcedureProduct ManagementCreating In-App ProductsI signed in to AppGallery Connect to enable the IAP service and set relevant parameters first. After configuring the key event notification recipient address for the service, I could create products by selecting my app and going to Operate > Products > Product Management.
IAP supports three types of products, that is, consumables, non-consumables, and subscriptions. For consumables that are depleted as they are used and are repurchasable, I created products including in-game currencies (coins or gems) and items (clothes and accessories). For non-consumables that are purchased once and will never expire, I created products that unlock special game levels or characters for my app. For subscriptions, I went with products such as a monthly game membership to charge users on a recurring basis until they decide to cancel them.
Aside from selecting the product type, I also needed to set the product ID, name, language, and price, and fill in the product description. Voilà. That's how I created the in-app products.
Global Adaptation of Product InformationHere's a good thing about IAP: developers don't need to manage multiple app versions for users from different countries or regions!
All I have to do is complete the multilingual settings of the products in AppGallery Connect. First, select the product languages based on the countries/regions the product is available in. Let's say English and Chinese, in this case. Then, fill in the product information in these two languages. The effect is roughly like this:
LanguageEnglishChineseProduct nameStealth skill card隐身技能卡Product descriptionHelps a user to be invisible so that they can outsurvive their enemies.帮助用户在紧急情况下隐身,打败敌人。
Now it's time to set the product price. I only need to set the price for one country/region and then IAP will automatically adjust the local price based on the exchange rate.
After the price is set, go to the product list page and click Activate. And that's it. The product has been adapted to different locations.
Purchase ImplementationChecking Support for IAPBefore using this kit, send an isEnvReady request to HMS Core (APK) to check whether my HUAWEI ID is located in the country/region where IAP is available. According to the kit's development documentation:
If the request result is successful, my app will obtain an IsEnvReadyResult instance, indicating that the kit is supported in my location.
If the request fails, an exception object will be returned. When the object is IapApiException, use its getStatusCode method to obtain the result code of the request. If the result code is OrderStatusCode.ORDER_HWID_NOT_LOGIN (no HUAWEI ID signed in), use the getStatus method of the IapApiException object to obtain a Status object, and use the startResolutionForResult method of Status to bring up the sign-in screen. Then, obtain the result in the onActivityResult method of Activity. Parse returnCode from the intent returned by onActivityResult. If the value of returnCode is OrderStatusCode.ORDER_STATE_SUCCESS, the country/region where the currently signed-in ID is located supports IAP. Otherwise, an exception occurs.
You guys can see my coding below.
Code:
// Obtain the Activity object.
final Activity activity = getActivity();
Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady();
task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {
@Override
public void onSuccess(IsEnvReadyResult result) {
// Obtain the execution result.
String carrierId = result.getCarrierId();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException) e;
Status status = apiException.getStatus();
if (status.getStatusCode() == OrderStatusCode.ORDER_HWID_NOT_LOGIN) {
// HUAWEI ID is not signed in.
if (status.hasResolution()) {
try {
// 6666 is a constant.
// Open the sign-in screen returned.
status.startResolutionForResult(activity, 6666);
} catch (IntentSender.SendIntentException exp) {
}
}
} else if (status.getStatusCode() == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) {
// The current country/region does not support IAP.
}
} else {
// Other external errors.
}
}
});
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 6666) {
if (data != null) {
// Call the parseRespCodeFromIntent method to obtain the result.
int returnCode = IapClientHelper.parseRespCodeFromIntent(data);
// Use the parseCarrierIdFromIntent method to obtain the carrier ID returned by the API.
String carrierId = IapClientHelper.parseCarrierIdFromIntent(data);
}
}
}
Showing ProductsTo show products configured to users, call the obtainProductInfo API in the app to obtain product details.
1. Construct a ProductInfoReq object, send an obtainProductInfo request, and set callback listeners OnSuccessListener and OnFailureListener to receive the request result. Pass the product ID that has been defined and taken effect to the ProductInfoReq object, and specify priceType for a product.
2. If the request is successful, a ProductInfoResult object will be returned. Using the getProductInfoList method of this object, my app can obtain the list of ProductInfo objects. The list contains details of each product, including its price, name, and description, allowing users to see the info of the products that are available for purchase.
Code:
List<String> productIdList = new ArrayList<>();
// Only those products already configured can be queried.
productIdList.add("ConsumeProduct1001");
ProductInfoReq req = new ProductInfoReq();
// priceType: 0: consumable; 1: non-consumable; 2: subscription
req.setPriceType(0);
req.setProductIds(productIdList);
// Obtain the Activity object.
final Activity activity = getActivity();
// Call the obtainProductInfo API to obtain the details of the configured product.
Task<ProductInfoResult> task = Iap.getIapClient(activity).obtainProductInfo(req);
task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {
@Override
public void onSuccess(ProductInfoResult result) {
// Obtain the product details returned upon a successful API call.
List<ProductInfo> productList = result.getProductInfoList();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException) e;
int returnCode = apiException.getStatusCode();
} else {
// Other external errors.
}
}
});
Initiating a PurchaseThe app can send a purchase request by calling the createPurchaseIntent API.
1. Construct a PurchaseIntentReq object to send a createPurchaseIntent request. Pass the product ID that has been defined and taken effect to the PurchaseIntentReq object. If the request is successful, the app will receive a PurchaseIntentResult object, and its getStatus method will return a Status object. The app will display the checkout screen of IAP using the startResolutionForResult method of the Status object.
Code:
// Construct a PurchaseIntentReq object.
PurchaseIntentReq req = new PurchaseIntentReq();
// Only the products already configured can be purchased through the createPurchaseIntent API.
req.setProductId("CProduct1");
// priceType: 0: consumable; 1: non-consumable; 2: subscription
req.setPriceType(0);
req.setDeveloperPayload("test");
// Obtain the Activity object.
final Activity activity = getActivity();
// Call the createPurchaseIntent API to create a product order.
Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntent(req);
task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {
@Override
public void onSuccess(PurchaseIntentResult result) {
// Obtain the order creation result.
Status status = result.getStatus();
if (status.hasResolution()) {
try {
// 6666 is a constant.
// Open the checkout screen returned.
status.startResolutionForResult(activity, 6666);
} catch (IntentSender.SendIntentException exp) {
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException) e;
Status status = apiException.getStatus();
int returnCode = apiException.getStatusCode();
} else {
// Other external errors.
}
}
});
2. After the app opens the checkout screen and the user completes the payment process (that is, successfully purchases a product or cancels the purchase), IAP will return the payment result to your app through onActivityResult. You can use the parsePurchaseResultInfoFromIntent method to obtain the PurchaseResultInfo object that contains the result information.
If the purchase is successful, obtain the purchase data InAppPurchaseData and its signature data from the PurchaseResultInfo object. Use the public key allocated by AppGallery Connect to verify the signature.
When a user purchases a consumable, if any of the following payment exceptions is returned, check whether the consumable was delivered.
Payment failure (OrderStatusCode.ORDER_STATE_FAILED).
A user has purchased the product (OrderStatusCode.ORDER_PRODUCT_OWNED).
The default code is returned (OrderStatusCode.ORDER_STATE_DEFAULT_CODE), as no specific code is available.
Code:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 6666) {
if (data == null) {
Log.e("onActivityResult", "data is null");
return;
}
// Call the parsePurchaseResultInfoFromIntent method to parse the payment result.
PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data);
switch(purchaseResultInfo.getReturnCode()) {
case OrderStatusCode.ORDER_STATE_CANCEL:
// The user cancels the purchase.
break;
case OrderStatusCode.ORDER_STATE_FAILED:
case OrderStatusCode.ORDER_PRODUCT_OWNED:
// Check whether the delivery is successful.
break;
case OrderStatusCode.ORDER_STATE_SUCCESS:
// The payment is successful.
String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData();
String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature();
// Verify the signature using your app's IAP public key.
// Start delivery if the verification is successful.
// Call the consumeOwnedPurchase API to consume the product after delivery if the product is a consumable.
break;
default:
break;
}
}
}
Confirming a PurchaseAfter a user pays for a purchase or subscription, the app checks whether the payment is successful based on the purchaseState field in InAppPurchaseData. If purchaseState is 0 (already paid), the app will deliver the purchased product or service to the user, then send a delivery confirmation request to IAP.
For a consumable, parse purchaseToken from InAppPurchaseData in JSON format to check the delivery status of the consumable.
After the consumable is successfully delivered and its purchaseToken is obtained, your app needs to use the consumeOwnedPurchase API to consume the product and instruct the IAP server to update the delivery status of the consumable. purchaseToken is passed in the API call request. If the consumption is successful, the IAP server will reset the product status to available for purchase. Then the user can buy it again.
Code:
// Construct a ConsumeOwnedPurchaseReq object.
ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq();
String purchaseToken = "";
try {
// Obtain purchaseToken from InAppPurchaseData.
InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
purchaseToken = inAppPurchaseDataBean.getPurchaseToken();
} catch (JSONException e) {
}
req.setPurchaseToken(purchaseToken);
// Obtain the Activity object.
final Activity activity = getActivity();
// Call the consumeOwnedPurchase API to consume the product after delivery if the product is a consumable.
Task<ConsumeOwnedPurchaseResult> task = Iap.getIapClient(activity).consumeOwnedPurchase(req);
task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() {
@Override
public void onSuccess(ConsumeOwnedPurchaseResult result) {
// Obtain the execution result.
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof IapApiException) {
IapApiException apiException = (IapApiException) e;
Status status = apiException.getStatus();
int returnCode = apiException.getStatusCode();
} else {
// Other external errors.
}
}
});
For a non-consumable, the IAP server returns the confirmed purchase data by default. After the purchase is successful, the user does not need to confirm the transaction, and the app delivers the product.
For a subscription, no acknowledgment is needed after a successful purchase. However, as long as the user is entitled to the subscription (that is, the value of InApppurchaseData.subIsvalid is true), the app should offer services.
ConclusionIt's a great feeling to make a game, and it's an even greater feeling when that game makes you money.
In this article, I shared my experience of building an in-app purchase function for my mobile survival game. To make it more suitable for a global market, I used some gimmicks from HMS Core In-App Purchases to configure product information in the language of the country or region where the user's account is located. In short, this streamlines the purchase journey for users wherever they are located.
Did I miss anything? I'm looking forward to hearing your ideas.
Mobile apps have significantly changed the way we live, bringing about greater convenience. With our mobiles we can easily book hotels online when we go sightseeing, buy train and flight tickets online for business trips, or just pay for a dinner using scan and pay.
There is rarely a one-app-fits-all approach of offering such services, so users have to switch back and forth between multiple apps. This also requires users to register and sign in to different apps, which is a trouble itself because users will need to complete complex registration process and repeatedly enter their account names and passwords.
In addition, as technology develops, a developer usually has multiple Android apps and app versions, such as the quick app and web app, for different platforms. If users have to repeatedly sign in to different apps or versions by the same developer, the churn rate will likely increase. What's more, the developer may need to even pay for sending SMS messages if users choose to sign in to their apps through SMS verification codes.
Is there anything the developer can do to streamline the sign-in process between different apps and platforms so that users do not need to enter their account names and passwords again and again?
Well fortunately, HMS Core Keyring makes this possible. Keyring is a Huawei service that offers credential management APIs for storing user credentials locally on users' Android phones and tablets and sharing the credentials between different apps and different platform versions of an app. Developers can call relevant APIs in their Android apps, web apps, or quick apps to use Keyring services, such as encrypt the sign-in credentials of users for local storage on user devices and share the credentials between different apps and platforms, thus creating a seamless sign-in experience for users across different apps and platforms. Besides, all credentials will be stored in Keyring regardless of which type of APIs developers are calling, to implement unified credential management and sharing.
In this article, I'll share how I used Keyring to manage and share sign-in credentials of users. I hope this will help you.
Advantages
First, I'd like to explain some advantages of Keyring.
Building a seamless sign-in experience
Your app can call Keyring APIs to obtain sign-in credentials stored on user devices, for easy sign-in.
Ensuring data security and reliability
Keyring encrypts sign-in credentials of users for local storage on user devices and synchronizes the credentials between devices via end-to-end encryption technology. The encrypted credentials cannot be decrypted on the cloud.
Reducing the churn rate during sign-in
Keyring can simplify the sign-in process for your apps, thus reducing the user churn rate.
Reducing the operations cost
With Keyring, you can reduce the operations cost, such as the expense for SMS messages used by users to sign in to your app.
Development Procedure
Next, let's look at how to integrate Keyring. Before getting started, you will need to make some preparations, such as register as a Huawei developer, generate and configure your signing certificate fingerprint in AppGallery Connect, and enable Keyring. You can click here to learn about the detailed preparation steps, which will not be introduced in this article.
After making necessary preparations, you can now start integrating the Keyring SDK. I'll detail the implementation steps in two scenarios.
User Sign-in Scenario
In this scenario, you need to follow the steps below to implement relevant logic.
1. Initialize the CredentialClient object in the onCreate method of your activity. Below is a code snippet example.
Code:
CredentialClient credentialClient = CredentialManager.getCredentialClient(this);
2. Check whether a credential is available. Below is a code snippet example.
Code:
List<AppIdentity> trustedAppList = new ArrayList<>();
trustedAppList.add(new AndroidAppIdentity("yourAppName", "yourAppPackageName", "yourAppCodeSigningCertHash"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "www.yourdomain.com"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "login.yourdomain.com"));
SharedCredentialFilter sharedCredentialFilter = SharedCredentialFilter.acceptTrustedApps(trustedAppList);
credentialClient.findCredential(sharedCredentialFilter, new CredentialCallback<List<Credential>>() {
@Override
public void onSuccess(List<Credential> credentials) {
if (credentials.isEmpty()) {
Toast.makeText(MainActivity.this, R.string.no_available_credential, Toast.LENGTH_SHORT).show();
} else {
for (Credential credential : credentials) {
}
}
}
@Override
public void onFailure(long errorCode, CharSequence description) {
Toast.makeText(MainActivity.this, R.string.query_credential_failed, Toast.LENGTH_SHORT).show();
}
});
3. Call the Credential.getContent method to obtain the credential content and obtain the result from CredentialCallback<T>. Below is a code snippet example.
Code:
private Credential mCredential;
// Obtained credential.
mCredential.getContent(new CredentialCallback<byte[]>() {
@Override
public void onSuccess(byte[] bytes) {
String hint = String.format(getResources().getString(R.string.get_password_ok),
new String(bytes));
Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
mResult.setText(new String(bytes));
}
@Override
public void onFailure(long l, CharSequence charSequence) {
Toast.makeText(MainActivity.this, R.string.get_password_failed,
Toast.LENGTH_SHORT).show();
mResult.setText(R.string.get_password_failed);
}
});
4. Call the credential saving API when a user enters a new credential, to save the credential. Below is a code snippet example.
Code:
AndroidAppIdentity app2 = new AndroidAppIdentity(sharedToAppName,
sharedToAppPackage, sharedToAppCertHash);
List<AppIdentity> sharedAppList = new ArrayList<>();
sharedAppList.add(app2);
Credential credential = new Credential(username, CredentialType.PASSWORD, userAuth,
password.getBytes());
credential.setDisplayName("user_niceday");
credential.setSharedWith(sharedAppList);
credential.setSyncable(true);
credentialClient.saveCredential(credential, new CredentialCallback<Void>() {
@Override
public void onSuccess(Void unused) {
Toast.makeText(MainActivity.this,
R.string.save_credential_ok,
Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(long errorCode, CharSequence description) {
Toast.makeText(MainActivity.this,
R.string.save_credential_failed + " " + errorCode + ":" + description,
Toast.LENGTH_SHORT).show();
}
});
User Sign-out Scenario
Similarly, follow the steps below to implement relevant logic.
1. Initialize the CredentialClient object in the onCreate method of your activity. Below is a code snippet example.
Code:
CredentialClient credentialClient = CredentialManager.getCredentialClient(this);
2. Check whether a credential is available. Below is a code snippet example.
Code:
List<AppIdentity> trustedAppList = new ArrayList<>();
trustedAppList.add(new AndroidAppIdentity("yourAppName", "yourAppPackageName", "yourAppCodeSigningCertHash"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "www.yourdomain.com"));
trustedAppList.add(new WebAppIdentity("youWebSiteName", "login.yourdomain.com"));
SharedCredentialFilter sharedCredentialFilter = SharedCredentialFilter.acceptTrustedApps(trustedAppList);
credentialClient.findCredential(sharedCredentialFilter, new CredentialCallback<List<Credential>>() {
@Override
public void onSuccess(List<Credential> credentials) {
if (credentials.isEmpty()) {
Toast.makeText(MainActivity.this, R.string.no_available_credential, Toast.LENGTH_SHORT).show();
} else {
for (Credential credential : credentials) {
// Further process the available credentials, including obtaining the credential information and content and deleting the credentials.
}
}
}
@Override
public void onFailure(long errorCode, CharSequence description) {
Toast.makeText(MainActivity.this, R.string.query_credential_failed, Toast.LENGTH_SHORT).show();
}
});
3. Call the deleteCredential method to delete the credential and obtain the result from CredentialCallback. Below is a code snippet example.
Code:
credentialClient.deleteCredential(credential, new CredentialCallback<Void>() {
@Override
public void onSuccess(Void unused) {
String hint = String.format(getResources().getString(R.string.delete_ok),
credential.getUsername());
Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(long errorCode, CharSequence description) {
String hint = String.format(getResources().getString(R.string.delete_failed),
description);
Toast.makeText(MainActivity.this, hint, Toast.LENGTH_SHORT).show();
}
});
Keyring offers two modes for sharing credentials: sharing credentials using API parameters and sharing credentials using Digital Asset Links. I will detail the two modes below.
Sharing Credentials Using API Parameters
In this mode, when calling the saveCredential method to save credentials, you can call the setSharedWith method to set parameters of the Credential object, to implement credential sharing. A credential can be shared to a maximum of 128 apps.
The sample code is as follows:
Code:
AndroidAppIdentity app1 = new AndroidAppIdentity("your android app name",
"your android app package name", "3C:99:C3:....");
QuickAppIdentity app2 = new QuickAppIdentity("your quick app name",
"your quick app package name", "DC:99:C4:....");
List<AppIdentity> sharedAppList = new ArrayList<>(); // List of apps with the credential is shared.
sharedAppList.add(app1);
sharedAppList.add(app2);
Credential credential = new Credential("username", CredentialType.PASSWORD, true,
"password".getBytes());
credential.setSharedWith(sharedAppList); // Set the credential sharing relationship.
credentialClient.saveCredential(credential, new CredentialCallback<Void>() {
@Override
public void onSuccess(Void unused) {
Toast.makeText(MainActivity.this,
R.string.save_credential_ok,
Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(long errorCode, CharSequence description) {
Toast.makeText(MainActivity.this,
R.string.save_credential_failed + " " + errorCode + ":" + description,
Toast.LENGTH_SHORT).show();
}
});
Sharing Credentials Using Digital Asset Links
In this mode, you can add credential sharing relationships in the AndroidManifest.xml file of your Android app. The procedure is as follows:
1. Add the following content to the <application> element in the AndroidManifest.xml file:
Code:
<application>
<meta-data
android:name="asset_statements"
android:value="@string/asset_statements" />
</application>
2. Add the following content to the res\values\strings.xml file:
Code:
<string name="asset_statements">your digital asset links statements</string>
The Digital Asset Links statements are JSON strings comply with the Digital Asset Links protocol. The sample code is as follows:
Code:
[{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://developer.huawei.com" // Set your website domain name.
}
},
{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "your android app package name",
"sha256_cert_fingerprints": [
"F2:52:4D:..."
]
}
},
{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "quick_app",
"package_name": "your quick app package name",
"sha256_cert_fingerprints": [
"C3:68:9F:..."
]
}
}
]
The relation attribute has a fixed value of ["delegate_permission/common.get_login_creds"], indicating that the credential is shared with apps described in the target attribute.
And that's all for integrating Keyring. That was pretty straightforward, right? You can click here to find out more about Keyring and try it out.
Conclusion
More and more developers are prioritizing the need for a seamless sign-in experience to retain users and reduce the user churn rate. This is especially true for developers with multiple apps and app versions for different platforms, because it can help them share the user base of their different apps. There are many ways to achieve this. As I illustrated earlier in this article, my solution for doing so is to integrate Keyring, which turns out to be very effective. If you have similar demands, have a try at this service and you may be surprised.
Did I miss anything? Let me know in the comments section below.