{
"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"
}
The first scriptable xposed module, provides a new way to change the application behavior.
Powered by Lua and made with ♥
Features
Lua scripting
Simple and intuitive hook API
Share your package with others by publish it at WeiJu2-Scripts
Q&AHow to write a hook?
Code:
local Toast = import("android.widget.Toast")
local Activity = import("android.app.Activity")
local Bundle = import("android.os.Bundle")
local StringBuilder = import("java.lang.StringBuilder")
hook {
class = Activity,
returns = void,
method = "onCreate",
params = {
Bundle
},
after = function(this, params)
-- This will call the `StringBuilder(CharSequence seq)` constructor
-- to instantiate a StringBuilder object
local sb = StringBuilder("Hello, ")
sb:append("WeiJu2")
Toast:makeText(this, sb:toString(), Toast.LENGTH_SHORT):show()
-- ^
-- Note: `this` is the Activity instance
end,
}
How to modify class fields?
Code:
-- With the `import` function you can bind any java class, and access all the fields and methods that defined
-- in that class. No more `XposedHelper.setStaticObjectField(Build.class, "DEVICE", "coral")` much cleaner!
local Build = import("android.os.Build")
Build.DEVICE = "coral"
Build.PRODUCT = "coral"
Build.MODEL = "Google Pixel 4XL"
Build.BRAND = "google"
Build.MANUFACTURER = "google"
Build.VERSION.RELEASE = "13"
How to import a package?
Code:
require("ikws4.system_variable").setup {
-- configs goes here
}
How to create a package?
A basic package template:
Code:
--[[
@metadata
return {
name = "my_package",
author = "you",
version = "1.0.0",
description = "Describle your package"
}
@end
--]]
local config = {
}
local M = {}
M.setup = function(opts)
config = table.extend(config, opts or {})
end
return M
Want to share your work with others? Create a PR at WeiJu2-Scripts
Screenshots
Video guide
DownloadGithub release: nightly build
zhipingne said:
scriptable xposed module
Click to expand...
Click to collapse
Looks good
i wish you good luck
Hi @zhipingne
Since from what i understand, it is possible to inject Lua code via Xposed with your application, in your opinion would it be possible to act on all applications embedding web browsers to navigate on web pages adapted to mobiles?
Example: inject CSS rules globally on all user applications to affect some like Spotify, Discord, Teams and others?
If it's possible I'll have fun!
Rom said:
Hi @zhipingne
Since from what i understand, it is possible to inject Lua code via Xposed with your application, in your opinion would it be possible to act on all applications embedding web browsers to navigate on web pages adapted to mobiles?
Example: inject CSS rules globally on all user applications to affect some like Spotify, Discord, Teams and others?
If it's possible I'll have fun!
Click to expand...
Click to collapse
Sorry for the late response, I was experiment a way to inject custom css into WebView in the last two days, And finally got a working demo. (It took so mush time because of the `object` function, in order to proxy a concrete class we need to generate dex file at runtime lol)
FYI, I followed this article: https://medium.com/@fanisveizis/usi...d-js-scripts-on-android-webviews-6da0502d6595
UPDATE
It stop working... The `dex` file that generate at runtime does not loaded back properly in the next launch time.
zhipingne said:
Sorry for the late response, I was experiment a way to inject custom css into WebView in the last two days, And finally got a working demo. (It took so mush time because of the `object` function, in order to proxy a concrete class we need to generate dex file at runtime lol)
FYI, I followed this article: https://medium.com/@fanisveizis/usi...d-js-scripts-on-android-webviews-6da0502d6595
UPDATE
It stop working... The `dex` file that generate at runtime does not loaded back properly in the next launch time.
Click to expand...
Click to collapse
Ok, do you think it is possible to achieve something functional in the end?
I would be very interested.
Thx
Rom said:
Ok, do you think it is possible to achieve something functional in the end?
I would be very interested.
Thx
Click to expand...
Click to collapse
Yes, I think it's possible
When i think about it, in fact your module can become as powerful as substratum if we want?
Code:
{
"builtin": false,
"collection": "Privacy",
"group": "Use.Tracking",
"name": "DeviceIntegrityCheck\/checkIntegrity",
"author": "B!",
"version": 0,
"description": "Skip asitplus device integrity check",
"className": "at.asitplus.utils.deviceintegrity.DeviceIntegrityCheck",
"methodName": "checkIntegrity",
"parameterTypes": [],
"returnType": "void",
"minSdk": 1,
"maxSdk": 999,
"minApk": 0,
"maxApk": 2147483647,
"enabled": true,
"optional": false,
"usage": true,
"notify": false,
"luaScript": "function before(hook, param)\n param:setResult(nil)\n return true\nend\n"
}
(Thanks to B!GBOY)
Is it possible to run such a XPrivacyLua script? If so, how formatted?
maxafe said:
Code:
{
"builtin": false,
"collection": "Privacy",
"group": "Use.Tracking",
"name": "DeviceIntegrityCheck\/checkIntegrity",
"author": "B!",
"version": 0,
"description": "Skip asitplus device integrity check",
"className": "at.asitplus.utils.deviceintegrity.DeviceIntegrityCheck",
"methodName": "checkIntegrity",
"parameterTypes": [],
"returnType": "void",
"minSdk": 1,
"maxSdk": 999,
"minApk": 0,
"maxApk": 2147483647,
"enabled": true,
"optional": false,
"usage": true,
"notify": false,
"luaScript": "function before(hook, param)\n param:setResult(nil)\n return true\nend\n"
}
(Thanks to B!GBOY)
Is it possible to run such a XPrivacyLua script? If so, how formatted?
Click to expand...
Click to collapse
Code:
local DeviceIntegrityCheck = import("at.asitplus.utils.deviceintegrity.DeviceIntegrityCheck")
hook {
class = DeviceIntegrityCheck,
returns = void,
method = "checkIntegrity",
before = function(this, params)
return nil
end
}
Maybe, I can write a convertor for this.
@zhipingne
Awesome. Worked right away. Thank you very much!
The version is not available...
xerel89 said:
The version is not available...
Click to expand...
Click to collapse
I'm sorry for the late response.
What do you mean not available? Is it can not be downloaded?
If so, here is the google drive link https://drive.google.com/file/d/1upurJDTJ5nS3RodsqLTCJKFg9daN4EaH/view?usp=sharing
zhipingne said:
I'm sorry for the late response.
What do you mean not available? Is it can not be downloaded?
If so, here is the google drive link https://drive.google.com/file/d/1upurJDTJ5nS3RodsqLTCJKFg9daN4EaH/view?usp=sharing
Click to expand...
Click to collapse
Thank you for this module, it looks awesome! Do you think it would be possible to use it to implement java code, such as a java script to disable the elastic overscroll in Android 12+? There is an example in java located here that I would like to test.
https://github.com/ionic-team/capacitor/issues/5384
jal3223 said:
Thank you for this module, it looks awesome! Do you think it would be possible to use it to implement java code, such as a java script to disable the elastic overscroll in Android 12+? There is an example in java located here that I would like to test.
https://github.com/ionic-team/capacitor/issues/5384
Click to expand...
Click to collapse
Yes, you can translate java code in to WeiJu2 scripts, here is an example:
Code:
local BridgeActivity = import("com.getcapacitor.BridgeActivity")
hook {
class = BridgeActivity,
returns = void,
method = "onStart",
after = function(this, params)
local webview = this:getBridge():getWebView();
webview:setOverScrollMode(webview.OVER_SCROLL_NEVER);
end
}
Note: I did't test the code, it just an example to show it's possible to implement java code using WeiJu2 hook.
UPDATE
How to disable Programmatically Android 12 Elastic Scroll Animation
how can I turn off the elastic scroll effect that comes with android 12? Best regards
stackoverflow.com
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;drc=c645853ab73ac8c5889b42f4ce7dc9353ee8fd35;bpv=1;bpt=1;l=3657
I think this code will make all view non over scrollable.
Code:
local View = import("android.view.View")
hook {
class = View,
returns = void,
method = "setOverScrollMode",
before = function(this, params)
local OVER_SCROLL_NEVER = 2
params[1] = OVER_SCROLL_NEVER
end
}
zhipingne said:
Yes, you can translate java code in to WeiJu2 scripts, here is an example:
Code:
local BridgeActivity = import("com.getcapacitor.BridgeActivity")
hook {
class = BridgeActivity,
returns = void,
method = "onStart",
after = function(this, params)
local webview = this:getBridge():getWebView();
webview:setOverScrollMode(webview.OVER_SCROLL_NEVER);
end
}
Note: I did't test the code, it just an example to show it's possible to implement java code using WeiJu2 hook.
UPDATE
How to disable Programmatically Android 12 Elastic Scroll Animation
how can I turn off the elastic scroll effect that comes with android 12? Best regards
stackoverflow.com
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;drc=c645853ab73ac8c5889b42f4ce7dc9353ee8fd35;bpv=1;bpt=1;l=3657
I think this code will make all view non over scrollable.
Code:
local View = import("android.view.View")
hook {
class = View,
returns = void,
method = "setOverScrollMode",
before = function(this, params)
local OVER_SCROLL_NEVER = 2
params[1] = OVER_SCROLL_NEVER
end
}
Click to expand...
Click to collapse
Thank you for the reply. That's awesome to hear. I'm going to keep messing around with it to see if I can disable the overscroll then. The code that you provided unfortunately doesn't work for all apps. It does work for some however. I believe there is another hook somewhere that must be disabled as well. If you have any ideas they would be greatly appreciated.
Thank you again for the app. It's amazing!
jal3223 said:
Thank you for the reply. That's awesome to hear. I'm going to keep messing around with it to see if I can disable the overscroll then. The code that you provided unfortunately doesn't work for all apps. It does work for some however. I believe there is another hook somewhere that must be disabled as well. If you have any ideas they would be greatly appreciated.
Thank you again for the app. It's amazing!
Click to expand...
Click to collapse
Try this to see if it works
Code:
local View = import "android.view.View"
local OVER_SCROLL_NEVER = 2
hook {
class = View,
params = {
Context,
AttributeSet,
int,
},
after = function(this, params)
this.mOverScrollMode = OVER_SCROLL_NEVER
end,
}
hook {
class = View,
returns = void,
method = "setOverScrollMode",
before = function(this, params)
params[1] = OVER_SCROLL_NEVER
end,
}
zhipingne said:
Try this to see if it works
Code:
local View = import "android.view.View"
local OVER_SCROLL_NEVER = 2
hook {
class = View,
params = {
Context,
AttributeSet,
int,
},
after = function(this, params)
this.mOverScrollMode = OVER_SCROLL_NEVER
end,
}
hook {
class = View,
returns = void,
method = "setOverScrollMode",
before = function(this, params)
params[1] = OVER_SCROLL_NEVER
end,
}
Click to expand...
Click to collapse
Thank you for the attempt, but unfortunately, that doesn't seem to fix it either. I've asked multiple developers to see if they could sort it and nobody has been able to so far. I don't know how Google implemented it so deep, but it seems very difficult to remove the "accordion" scroll. Thank you for trying though. If you have any ideas though, they would be greatly appreciated.
zhipingne said:
I'm sorry for the late response.
What do you mean not available? Is it can not be downloaded?
If so, here is the google drive link https://drive.google.com/file/d/1upurJDTJ5nS3RodsqLTCJKFg9daN4EaH/view?usp=sharing
Click to expand...
Click to collapse
Thanks! The apk isn't posted on the first post or on github.
This does seem like a replacement for XprivacyLua which just stopped being supported.
It work perfectly! Thank you very much.
One more thing, Is there a way we can load script from local file instead of write directly on module interface, please?
wanting2521 said:
It work perfectly! Thank you very much.
One more thing, Is there a way we can load script from local file instead of write directly on module interface, please?
Click to expand...
Click to collapse
File access causes me a lot of headache, and current implementation still have some flaw it.
Why would want load script from file?
ref: https://github.com/ikws4/WeiJu2/issues/7#issuecomment-1248109818
Related
I need help with this code that I've been trying to figure out for hours. I know it's something close to what I have from my research, but I can't get it to work. Not even the codes provided from other posts on other sites. Any ideas?
public void checkEventsInCal() {
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(Uri.parse("content://com.android.calendar/events"), new String[]{"_id"}, "_id=?", new String[]{CalendarContract.Events.TITLE}, null);
if (cursor.moveToFirst()) {
Toast.makeText(this,"Event Exists!", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(this,"Event Doesn't Exist!", Toast.LENGTH_SHORT).show();
}
}
Thanks in advance for your help!!
Can I get a working solution please. Someone please respond. Thanks!
More information like this, you can visit HUAWEI Developer Forum
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201326975480310022&fid=0101246461018590361
Hello everyone , in this article we’re going to take a look at the Huawei Health Kit features so we can easily put these features to our apps.
Health app industry have shown an important increase in recent years.The software and healthcare industry are working together to find the latest technology products for the needs of patients and families.Huawei offers high technology service for the health and fitness app ecosystem with HealthKit and HiHealthKit service.Huawei and third-party partner capabilities track and customize all aspects of better living.
Data storage : Provides a platform for users to store their fitness and health data.
Data openness : Provides a wide range of APIs for writing and reading speed, positioning, blood sugar level, and other data.
Data interaction : Enables you and your users to agree on data access, guaranteeing proper use and security of personal data.
HealthKit:
https://developer.huawei.com/consumer/en/hms/huawei-healthkit
HiHealth:
https://developer.huawei.com/consumer/en/hms/huawei-healthkit
We will examine the differences between HealthKit and HiHealth in our next articles.I created a sample app which I’ve open-sourced on GitHub for enthusiasts who want to use this service.
https://github.com/tualdev/hms-health-kit-demo
Setting up the Health Kit
You’ll need to do setup before you can use the Health Kit.You can follow the official documentation on how to prepare app and enable Health Kit on App Gallery Connect.
Also you can follow this blog post to integrate your apps with Huawei HMS Core:
https://medium.com/huawei-developers/android-integrating-your-apps-with-huawei-hms-core-1f1e2a090e98
I’ll show you how to implement Data Controller features in this article for your android projects.Let’s get started!
Add the required dependencies to the build.gradle file under app folder.We need some dependencies.
Code:
implementation 'com.huawei.hms:hwid:4.0.1.300'
implementation 'com.huawei.agconnect:agconnect-core:1.3.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.3.1.300'
implementation 'com.huawei.hms:hihealth-base:5.0.0.300'
Add the required permissions to the AndroidManifest.xml file under app/src/main folder.
Code:
<uses-permission android:name="android.permission.INTERNET" />
You need to give some permissions after applying for HealthKit in service cards area.The applied permissions will be displayed on the user authorization page.
{
"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"
}
Let’s continue by coding Data Controller functionalities in our project.
Sign-in before using Data Controller
Firstly, we need to configure Huawei ID sign in.After that, we will start sign intent while using startActivityForResult method.We added also some scopes to apply for. The following only shows an example. You need to add scopes according to your specific needs.
Code:
huaweiIdAuthParamsHelper = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
val scopeList = listOf(
Scope(HwIDConstant.SCOPE.SCOPE_ACCOUNT_EMAIL),
Scope(HwIDConstant.SCOPE.ACCOUNT_BASEPROFILE),
Scope(Scopes.HEALTHKIT_STEP_BOTH),
Scope(Scopes.HEALTHKIT_HEIGHTWEIGHT_BOTH),
Scope(Scopes.HEALTHKIT_HEARTRATE_BOTH)
)
huaweiIdAuthParamsHelper.setScopeList(scopeList)
authParams = huaweiIdAuthParamsHelper.setAccessToken().createParams()
authService = HuaweiIdAuthManager.getService(this, authParams)
HEALTHKIT_STEP_BOTH → View and save step counts in HUAWEI Health Kit.
HEALTHKIT_HEIGHTWEIGHT_BOTH → View and save height and weight in HUAWEI Health Kit.
HEALTHKIT_HEARTRATE_BOTH → View and save the heart rate data in HUAWEI Health Kit.
For details about the mapping between scopes and permissions, see Scope in the API reference.After successfully sign process , we can move to crud and other operations. We are able to call ten methods in DataController to perform operations on the fitness and health data.The methods include:
insert: inserts data.
delete: deletes data.
update: updates data.
read: reads data.
readTodaySummation: queries the summarized data of the current day.
readTodaySummationFromDevice: queries the summarized data of the local device of the current day.
registerModifyDataMonitor: registers a listener for data updates.
unregisterModifyDataMonitor: unregisters a listener for data updates.
syncAll: syncs data between the device and cloud.
clearAll: clears data of the app from the device and cloud.
We need to create the data controller object before starting operations.
Code:
val hiHealthOptions = HiHealthOptions.builder()
.addDataType(DataType.DT_CONTINUOUS_STEPS_DELTA, HiHealthOptions.ACCESS_READ)
.addDataType(DataType.DT_CONTINUOUS_STEPS_DELTA, HiHealthOptions.ACCESS_WRITE)
.addDataType(DataType.DT_INSTANTANEOUS_HEIGHT, HiHealthOptions.ACCESS_READ)
.addDataType(DataType.DT_INSTANTANEOUS_HEIGHT, HiHealthOptions.ACCESS_WRITE)
.addDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT, HiHealthOptions.ACCESS_READ)
.addDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT, HiHealthOptions.ACCESS_WRITE)
.build()
val signInHuaweiId = HuaweiIdAuthManager.getExtendedAuthResult(hiHealthOptions)
dataController = HuaweiHiHealth.getDataController(this, signInHuaweiId)
Inserting Data
We build a DataCollector object to insert body weight.
Code:
val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
.setDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
.setDataStreamName("BODY_WEIGHT_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build()
We will create a sampling dataset based on the data collector.
Code:
val sampleSet = SampleSet.create(dataCollector)
We will create the start time, end time, and weight value for a DT_INSTANTANEOUS_BODY_WEIGHT sampling point.The weight value must be in float format.
Code:
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault())val currentDate = dateFormat.format(Date())val startDate: Date = dateFormat.parse(currentDate)val endDate: Date = dateFormat.parse(currentDate)val samplePoint = sampleSet.createSamplePoint().setTimeInterval(startDate.time, endDate.time, TimeUnit.MILLISECONDS)samplePoint.getFieldValue(Field.FIELD_BODY_WEIGHT).setFloatValue(weight)
sampleSet.addSample(samplePoint)
And in this time, we need to call the data controller to insert the sampling dataset into the Health platform.
Code:
val insertTask: Task = dataController.insert(sampleSet)
insertTask.addOnSuccessListener {
Log.i("HomeFragment","Success insert a SampleSet into HMS core")
showSampleData(sampleSet, "Insert Weight")
}.addOnFailureListener { e ->
Toast.makeText(this, e.message.toString(), Toast.LENGTH_LONG).show()
}
If you examine the showSampleData function, you can see how the added data was reached.
Code:
private fun showSampleData(sampleSet: SampleSet, desc: String) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
for (samplePoint in sampleSet.samplePoints) {
Log.i("HomeFragment","Sample point type: " + samplePoint.dataType.name)
Log.i("HomeFragment", "Start: " + dateFormat.format(Date(samplePoint.getStartTime(TimeUnit.MILLISECONDS))))
Log.i("HomeFragment", "End: " + dateFormat.format(Date(samplePoint.getEndTime(TimeUnit.MILLISECONDS))))
for (field in samplePoint.dataType.fields) {
Log.i("HomeFragment", "Field: " + field.name + " Value: " + samplePoint.getFieldValue(field))
Toast.makeText(this, desc + samplePoint.getFieldValue(field), Toast.LENGTH_LONG).show()
}
}
}
Deleting Data
We will build a DataCollector object for data deletion.
Code:
val dataCollector: DataCollector = DataCollector.Builder().setPackageName(this)
.setDataType(DataType.DT_INSTANTANEOUS_BODY_WEIGHT)
.setDataStreamName("BODY_WEIGHT_DELTA")
.setDataGenerateType(DataCollector.DATA_TYPE_RAW)
.build()
After creating data collector object, we need to build the time range for the deletion: start time and end time.
Code:
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault())
val startDate: Date = dateFormat.parse(startTimeEditText.text.toString())
val endDate: Date = dateFormat.parse(endTimeEditText.text.toString())
We build a parameter object as the conditions for the deletion while using DeleteOptions.So, the data in this time interval will be deleted.Also note that: Only historical data that has been inserted by the current app can be deleted from the Health platform.
Code:
val deleteOptions = DeleteOptions.Builder()
.addDataCollector(dataCollector)
.setTimeInterval(startDate.time, endDate.time, TimeUnit.MILLISECONDS)
.build()
And as last step, we use the specified condition deletion object to call the data controller to delete the sampling dataset.
Calling the data controller to delete the sampling dataset is an asynchronous operation. Therefore, a listener needs to be registered to monitor whether the data deletion is successful or not.
Code:
val deleteTask = dataController.delete(deleteOptions)
deleteTask.addOnSuccessListener {
Log.i("HomeFragment","Success delete steps data")
Toast.makeText([email protected], "Delete success", Toast.LENGTH_LONG).show()
}.addOnFailureListener {
Log.v(TAG, it.message.toString())
}
Updating Data
We will build a DataCollector object for data update.
This is not the end. For full content, you can visit https://forums.developer.huawei.com/forumPortal/en/home
What is the difference between Huawei Health kit and Hihealth kit ?
Hello. Is there a way to correct data in the Huawei Health database?. For instance remove steps from a certain date?. Thank you!
More information like this, you can visit HUAWEI Developer Forum
Original link:https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201327649924660033&fid=0101187876626530001
{
"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"
}
If you have always wanted to make your own music player somewhere inside of you, this tutorial is for you. We will use Huawei’s relatively new AudioKit to develop one and I will show the steps one by one so that you can develop a music player on your own.
Do not rely on copy-pastes though, I am here to guide you, not let you copy-paste my code. To do that, you do not have to read this extensive tutorial, just click here to reach the full source code. Keep in mind that this source code contains three other HMS kits and their imperfect implementations, it is not a dedicated AudioKit-only app. However, if you are here to learn for real, let’s start below.
First of all, make sure you have everything you needed before starting to develop your app.
Hardware Requirements
A computer (desktop or laptop) running Windows 7 or Windows 10
A Huawei phone (with the USB cable), which is used for debugging
Software Requirements
Java JDK (JDK 1.7 is recommended.)
Android Studio 3.X
SDK Platform 19 or later
Gradle 4.6 or later
HMS Core (APK) 5.0.0.300 or later
Required Knowledge
Android app development basics
Android app development multithreading
Secondly, you should integrate Huawei HMS Core to your app. Details are listed here, in the official documentation. You should complete #3 and #4 until “Writing the Code” part. From now on, I will assume that you have prepared your application and ready to go with necessary devices attached for running.
Apart from this tutorial, you can always use sample app provided by Huawei, by clicking here. Source code gives idea about most concepts but it is not well-explained and the code is kind of confusing. My tutorial will also use that sample app but it will not be the same thing at all.
For app resources like play button, pause button, skip button etc., please find your own resources or use the resources provided by Huawei sample app that I provided the link above. If you already have resources available, you can use them too because it is very important to have a responsive UI to track music changes, but ultimately it does not matter how they actually look. From now on, I will also assume that you have necessary drawables/images/buttons for a standard music player. If you are going to use the app for commercial purposes, make sure the resources you use have no copyright issues.
End Product
If you want to know what kind of product we will be having in the end, look no further. The below screenshot roughly describes our end product after this relatively long tutorial. Of course, all customizations and UI elements (including colors) are up to you, so it could differ from my product.
Also, as you can see my app contains AdsKit and Account Kit usages, which will not be included in this tutorial. That’s why, I will not give you the XML codes of my application (I already shared the my GitHub project link, if you are so inclined).
Remark: I mostly use the words audio file, music file and song(s) interchangably. Please do not get confused.
Let’s Plan!
Let’s plan together. We want to develop a music player application, so it should has some basic features that we want to support, such as play/pause, next/previous audio and seekbar updates. Seekbar will be showing us the progress of the music and it should be updated real time so that our music playback is always synced with life cycles of the app as well as the seekbar of our app. If you want to go further, we should add play modes, shuffle audio files, normal mode (sequential playback), loop the audio and loop the playlist. Of course, we want to render our cover image to the screen for a nice user experience and print the details of the audio file below it. And since we are only implementing the core features, we should have a playlist icon to open up the playlist and we should be able to choose an audio file from it. For the sake of simplicity, all browsed audio files from the device will be added to one single playlist for users to choose from. For the scope of this tutorial, creating new playlists, adding/removing files to those lists etc. are not supported. As can be predicted, our code will include a lot of button clicks.
I suggest you start with design on your own. Assuming that you have button/image resources you can imitate the design of my app (screenshot above) or your favorite music player. I placed the playlist button right above and clicking on it should open the playlist, and re-clicking should close it back. You can ignore the test ad below the playback buttons and AccountKit related parts above the cover image.
Let me remind you that I use view binding, so it is natural that you will not see much “findViewById”s. For official documentation of what I do, follow here.
Let’s code!
We should initialize our AudioKit managers to manage the playback later. Then, we should browse the local storage and get the audio files. After also completing listeners and notifications, we should implement button clicks so that users could have a smooth experience. Let’s start with a custom method called initializeManagerAndGetPlayList(…) to do the actual work, it is to be called in onCreate of the activity.
Code:
@SuppressLint("StaticFieldLeak")
public void initializeManagerAndGetPlayList(final Context context) {
new AsyncTask() {
@Override
protected Void doInBackground(Void... voids) {
HwAudioPlayerConfig hwAudioPlayerConfig = new HwAudioPlayerConfig(context);
HwAudioManagerFactory.createHwAudioManager(hwAudioPlayerConfig, new HwAudioConfigCallBack() {
@RequiresApi(api = Build.VERSION_CODES.R)
@Override
public void onSuccess(HwAudioManager hwAudioManager) {
try {
mHwAudioManager = hwAudioManager;
mHwAudioPlayerManager = hwAudioManager.getPlayerManager();
mHwAudioConfigManager = hwAudioManager.getConfigManager();
mHwAudioQueueManager = hwAudioManager.getQueueManager();
playList = getLocalPlayList(MainActivity.this);
if (playList.size() > 0) {
Collections.sort(playList, new Comparator() {
@Override
public int compare(final HwAudioPlayItem object1, final HwAudioPlayItem object2) {
return object1.getAudioTitle().compareTo(object2.getAudioTitle());
}
});
}
doListenersAndNotifications(MainActivity.this);
} catch (Exception e) {
Log.e("TAG", "player init fail", e);
}
}
@Override
public void onError(int errorCode) {
Log.e("TAG", "init err:" + errorCode);
}
});
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
PlaylistAdapter playlistAdapter = new PlaylistAdapter(playList);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
binding.playlistRecyclerView.setLayoutManager(layoutManager);
playlistAdapter.setOnItemClickListener(MainActivity.this);
binding.playlistRecyclerView.setAdapter(playlistAdapter);
super.onPostExecute(aVoid);
}
}.execute();
}
Let me explain what we do here. We need a thread operation to create the managers and get the configuration in AudioKit. Thus, I used AsyncTask to do just that.
Remark: AsyncTask is currently deprecated so I would suggest you to use other methods if you care about modernity.
If AsyncTask succeeds, then I get my HwAudioManager instance and set it to my global variable. After that, using that manager instance, I get my player and queue managers. (and config manager as well but it will be used less frequently)
After getting my managers, I get my local playlist by a method I will share below. The collections code is just to sort the list alphabetically, you do not have to use it. Later, there is another method called doListenersAndNotifications(…), to set the notification bar and to attach the listeners, which is a crucial part in playback management.
In onPostExecute method, now that my managers are ready, I set my adapter for my playlist. However, we will get back to this later on. You do not need to worry about for now.
Let’s see how getLocalPlayList(…) works.
Beware: In order for below method to work, you must ask for the storage permission from the user explicitly. How to deal with it is your own responsibility. You must consider all cases (like user not giving consent etc.). The below method will work only after the storage permission is granted.
Code:
public List getLocalPlayList(Context context) {
List playItemList = new ArrayList<>();
Cursor cursor = null;
try {
ContentResolver contentResolver = context.getContentResolver();
if (contentResolver == null) {
return playItemList;
}
String selection = MediaStore.Audio.Media.IS_MUSIC + "!=0";
cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,
selection,
null,
MediaStore.Audio.Media.TITLE);
HwAudioPlayItem songItem;
if (cursor != null) {
if(!cursor.moveToNext()){
//there is no music, do sth
return playItemList; //return empty list for now
}
else{
while(cursor.moveToNext()) {
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
if (new File(path).exists()) {
songItem = new HwAudioPlayItem();
songItem.setAudioTitle(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)));
songItem.setAudioId(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)) + "");
songItem.setFilePath(path);
songItem.setOnline(0);
songItem.setIsOnline(0);
songItem.setDuration(cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));
songItem.setSinger(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));
playItemList.add(songItem);
}
}
}
}
else{
Toast.makeText(this, "We have a serious cursor problem here!", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Log.e("TAG,", "EXCEPTION", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
Log.i("LOCALITEMSIZE", "getLocalPlayList: " + playItemList.size());
return playItemList;
}
HwAudioPlayItem is the AudioKit’s POJO class for audio files. It contains the basic attributes of an audio file that developers can set, to use them later. Please note that, as of the publish time of this article, HwAudioPlayItem does not contain enough methods to initialize. Thus, some of the fields you consider you would use may be lacking. For example, there is no field for album name and thus my screenshot above always displays “Album Unknown”.
By this method, we browse the local music files, retrieve them in the format of HwAudioPlayItem and add them to a local list. Before returning, see the size of it in the logs. Our playlist is therefore ready, after this operation.
Now let’s see how I set listeners and implement notifications.
Code:
private List mTempListeners = new CopyOnWriteArrayList<>(); //a global variable
private void doListenersAndNotifications(final Context context) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
for (HwAudioStatusListener listener : mTempListeners) {
try {
mHwAudioManager.addPlayerStatusListener(listener);
} catch (RemoteException e) {
Log.e("TAG", "TAG", e);
}
}
mHwAudioConfigManager.setSaveQueue(true);
mHwAudioConfigManager.setNotificationFactory(new INotificationFactory() {
@Override
public Notification createNotification(NotificationConfig notificationConfig) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
builder = new NotificationCompat.Builder(getApplication(), null);
RemoteViews remoteViews = new RemoteViews(getApplication().getPackageName(), R.layout.notification_player);
builder.setContent(remoteViews);
builder.setSmallIcon(R.drawable.icon_notifaction_music);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
builder.setCustomBigContentView(remoteViews);
NotificationUtils.addChannel(getApplication(), NotificationUtils.NOTIFY_CHANNEL_ID_PLAY, builder);
boolean isQueueEmpty = mHwAudioManager.getQueueManager().isQueueEmpty();
Bitmap bitmap;
bitmap = notificationConfig.getBitmap();
setBitmap(remoteViews, bitmap);
boolean isPlaying = mHwAudioManager.getPlayerManager().isPlaying() && !isQueueEmpty;
remoteViews.setImageViewResource(R.id.image_toggle, isPlaying ? R.drawable.ic_notification_stop : R.drawable.ic_notification_play);
HwAudioPlayItem playItem = mHwAudioManager.getQueueManager().getCurrentPlayItem();
remoteViews.setTextViewText(R.id.text_song, playItem.getAudioTitle());
remoteViews.setTextViewText(R.id.text_artist, playItem.getSinger());
remoteViews.setImageViewResource(R.id.image_last, R.drawable.ic_notification_before);
remoteViews.setImageViewResource(R.id.image_next, R.drawable.ic_notification_next);
remoteViews.setOnClickPendingIntent(R.id.image_last, notificationConfig.getPrePendingIntent());
remoteViews.setOnClickPendingIntent(R.id.image_toggle, notificationConfig.getPlayPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.image_next, notificationConfig.getNextPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.image_close, getCancelPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.layout_content, getMainIntent());
return builder.build();
}
else{
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplication(), null);
RemoteViews remoteViews = new RemoteViews(getApplication().getPackageName(), R.layout.statusbar);
builder.setContent(remoteViews);
builder.setSmallIcon(R.drawable.icon_notifaction_music);
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
builder.setCustomBigContentView(remoteViews);
NotificationUtils.addChannel(getApplication(), NotificationUtils.NOTIFY_CHANNEL_ID_PLAY, builder);
boolean isQueueEmpty = mHwAudioManager.getQueueManager().isQueueEmpty();
Bitmap bitmap;
bitmap = notificationConfig.getBitmap();
setBitmap(remoteViews, bitmap);
boolean isPlaying = mHwAudioManager.getPlayerManager().isPlaying() && !isQueueEmpty;
remoteViews.setImageViewResource(R.id.widget_id_control_play,
isPlaying ? R.drawable.notify_btn_pause_selector : R.drawable.notify_btn_play_selector);
HwAudioPlayItem playItem = mHwAudioManager.getQueueManager().getCurrentPlayItem();
remoteViews.setTextViewText(R.id.trackname, playItem.getAudioTitle());
remoteViews.setTextViewText(R.id.artistalbum, playItem.getSinger());
remoteViews.setImageViewResource(R.id.widget_id_control_prev, R.drawable.notify_btn_close_selector);
remoteViews.setImageViewResource(R.id.widget_id_control_next, R.drawable.notify_btn_next_selector);
remoteViews.setOnClickPendingIntent(R.id.widget_id_control_prev, getCancelPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.widget_id_control_play, notificationConfig.getPlayPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.widget_id_control_next, notificationConfig.getNextPendingIntent());
remoteViews.setOnClickPendingIntent(R.id.statusbar_layout, getMainIntent());
return builder.build();
}
}
});
}
});
}
private void setBitmap(RemoteViews remoteViews, Bitmap bitmap) {
HwAudioPlayItem tmpItem = mHwAudioQueueManager.getCurrentPlayItem();
Bitmap imageCoverOfMusic = getBitmapOfCover(tmpItem);
if(imageCoverOfMusic != null){
Log.i("TAG", "Notification bitmap not empty");
remoteViews.setImageViewBitmap(R.id.image_cover, imageCoverOfMusic);
}
else{
if (bitmap != null) {
Log.i("TAG", "Notification bitmap not empty");
remoteViews.setImageViewBitmap(R.id.image_cover, bitmap);
} else {
Log.w("TAG", "Notification bitmap is null");
remoteViews.setImageViewResource(R.id.image_cover, R.drawable.icon_notifaction_default);
}
}
}
private Bitmap getAlbumImage(String path) {
try{
android.media.MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(path);
byte[] data = mmr.getEmbeddedPicture();
if (data != null)
return BitmapFactory.decodeByteArray(data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
public Bitmap getBitmapOfCover(HwAudioPlayItem currItem){
if(currItem != null) {
String currentSongPath = currItem.getFilePath();
if (currentSongPath != null) {
Bitmap tmpMap = getAlbumImage(currentSongPath);
binding.albumPictureImageView.setImageBitmap(tmpMap);
return tmpMap;
}
}
return null;
}
private PendingIntent getCancelPendingIntent() {
Log.i("TAG", "getCancelPendingIntent");
Intent closeIntent = new Intent("com.menes.audiokittryoutapp.cancel_notification");
closeIntent.setPackage(getApplication().getPackageName());
return PendingIntent.getBroadcast(getApplication(), 2, closeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private PendingIntent getMainIntent() {
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.LAUNCHER");
intent.setClass(getApplication().getBaseContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
return PendingIntent.getActivity(getApplication(), 0, intent, 0);
}
Let me explain here. I know it looks complicated but most of it is almost copy-paste boiler plate code. First we attach listeners in a looper, HwAudioStatusListener is AudioKit’s listener class. After that we save our queue and set the notification bar. I use this code with else part almost commented out due to support issues but you may copy and paste it to try it out in lower versions of Android. That being said, it should be clear that you should update the resource names as per your needs/wants.
setBitmap(…) method is to render the cover image in notification bar layout. (You can find the customized layout in GitHub link I shared). You cannot find this method in this way in sample apps. It is a cool and important feature, so I suggest you use it. Also, there is a NotificationUtils class for additional notification bar related codes, and you may also find it in the GitHub. You do not have to put them separately, you can copy those code to MainActivity and still use them, if you think that that will not be overcomplicating your code.
Also the other Bitmap methods will let you use the setBitmap(…) method correctly. Also, they will be useful when rendering the cover image to main screen too. Since HwAudioPlayItem class do not have a field for image paths for easy renders, I had to use more than 2 methods to use the path of the song to retrieve the cover image and then use it to get the Bitmap of it.
Other intent methods are to implement the feature of close button to cancel the notification and for the main intent.
The End for now!
The rest of the application will be talked about in Part 2 of this series. We will be implementing onCreate method, button clicks and listeners in detail. See you there.
Introduction
In-App linking is refer to App linking or deep linking in general, however it is particularly for the use case of sharing content with the same application for the same application installed on the android device.
{
"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"
}
Huawei App Linking leverage developers/users to create cross platform links which can work as defined and can be distributed with multiple channels to users.
When the user clicks the link, it will be re-directed to the specified in-app content.
Huawei App Linking has multiple functions:
1. Support for deferred deep links
2. Link display in card form
3. Data statistics
Huawei App Linking supports the link creation in multiple ways:
1. Creating a link in AppGallery Connect
2. Creating a link by calling APIs on the client
3. Manually constructing a link
Creating a link in AppGallery Connect
We will be focusing on the Huawei App linking capabilities to create the deep links for our Android application through Huawei AppGallery Connect.
Use Case
There could be multiple use cases for this capability, however I will throw some light on a use case which is most commonly adapted by the applications with complex UX and high transmission between different in-app pages.
Development Overview
1. Must have a Huawei Developer Account
2. Must have Android Studio 3.0 or later
3. Must have a Huawei phone with HMS Core 4.0.2.300 or later
4. EMUI 3.0 or later
Software Requirements
1. Java SDK 1.7 or later
2. Android 5.0 or later
Preparation
1. Create an app or project in Android Studio.
2. Create an app and project in the Huawei AppGallery Connect.
3. Provide the SHA Key and App Package name of the project for which you want the App Linking to be done (Example: News application)
4. Download the agconnect-services.json file and paste to the app folder of your android studio.
Integration
Add below to build.gradle (project)file, under buildscript/repositories and allprojects/repositories.
Java:
Maven {url 'http://developer.huawei.com/repo/'}
Add below to build.gradle (app) file, under dependencies to use the App Linking SDK.
Java:
dependencies{
// Import the SDK.
implementation 'com.huawei.agconnect:agconnect-applinking:1.4.1.300'
}
News Application
News application is developed with multiple content and complex UX to show the capability of the Huawei App Linking.
I will highlight some if the important code blocks for this application.
Main Activity
This activity is the entry point for the application which will have the navigation tab to handle the multiple news categories.
Also, it receives the App Link, read it and re-direct it to the corresponding content.
Java:
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private ViewPager viewPager;
FloatingActionButton AddLinks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
// Find the view pager that will allow the user to swipe between fragments
viewPager = findViewById(R.id.viewpager);
// Give the TabLayout the ViewPager
TabLayout tabLayout = findViewById(R.id.sliding_tabs);
tabLayout.setupWithViewPager(viewPager);
// Set gravity for tab bar
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
NavigationView navigationView = findViewById(R.id.nav_view);
assert navigationView != null;
navigationView.setNavigationItemSelectedListener(this);
// Set the default fragment when starting the app onNavigationItemSelected(navigationView.getMenu().getItem(0).setChecked(true));
// Set category fragment pager adapter
CategoryFragmentPagerAdapter pagerAdapter =
new CategoryFragmentPagerAdapter(this, getSupportFragmentManager());
// Set the pager adapter onto the view pager
viewPager.setAdapter(pagerAdapter);
AddLinks.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v)
shareLink("https://bulletin.dra.agconnect.link/yAvD")
}
});
//Receive and re-direct app link
recievelink();
}
//Receive applinks here
private void recievelink()
{ AGConnectAppLinking.getInstance().getAppLinking(getIntent()).addOnSuccessListener(new OnSuccessListener<ResolvedLinkData>() {
@Override
public void onSuccess(ResolvedLinkData resolvedLinkData) {
Uri deepLink1 = null;
if (resolvedLinkData != null) {
deepLink1 = resolvedLinkData.getDeepLink();
String myLink= deepLink1.toString();
if(myLink.contains("science")) {
viewPager.setCurrentItem(Constants.SCIENCE);
}
try {
Toast.makeText(MainActivity.this, deepLink1.toString(), Toast.LENGTH_SHORT).show();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e("LOG ", "Exception Occured : " + e.getMessage());
e.printStackTrace();
ApiException apiException = (ApiException) e;
Log.e("LOG ", "status code " + apiException.getStatusCode());
}
});
}
private void shareLink(String appLinking) {
if (appLinking != null) {
System.out.println("inside button click " + appLinking);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, appLinking);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
@Override
public void onBackPressed() {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
// Switch Fragments in a ViewPager on clicking items in Navigation Drawer
if (id == R.id.nav_home) {
viewPager.setCurrentItem(Constants.HOME);
} else if (id == R.id.nav_world) {
viewPager.setCurrentItem(Constants.WORLD);
} else if (id == R.id.nav_science) {
viewPager.setCurrentItem(Constants.SCIENCE);
} else if (id == R.id.nav_sport) {
viewPager.setCurrentItem(Constants.SPORT);
} else if (id == R.id.nav_environment) {
viewPager.setCurrentItem(Constants.ENVIRONMENT);
} else if (id == R.id.nav_society) {
viewPager.setCurrentItem(Constants.SOCIETY);
} else if (id == R.id.nav_fashion) {
viewPager.setCurrentItem(Constants.FASHION);
} else if (id == R.id.nav_business) {
viewPager.setCurrentItem(Constants.BUSINESS);
} else if (id == R.id.nav_culture) {
viewPager.setCurrentItem(Constants.CULTURE);
}
DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
// Initialize the contents of the Activity's options menu
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the Options Menu we specified in XML
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
// This method is called whenever an item in the options menu is selected.
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
Intent settingsIntent = new Intent(this, SettingsActivity.class);
startActivity(settingsIntent);
return true;
}
return super.onOptionsItemSelected(item);
}
}
Creating a link in AppGallery Connect to directly open the Science tab for the News Application through a link
1. Login to AppGallery Connect.
2. Choose My Projects > NewsWorld(App Linking).
3. Go to>Growing > App Linking>Enable now
4. Once you enable the app linking for your project by setting up the country and region choose URL prefix>Add URL prefix
Add URL prefix, which is a free domain name provided by AppGallery Connect with a string.
Tip: URL prefix should be unique and can contain only lowercase letters and digits.
Select Set domain name option and then click on Next button.
The following page will be displayed.
6. Click App Linking and then click Create App Linking for deep linking purpose. It is required to directly navigate to specific in-app content from a different application.
7. It will suggest short link by itself or you can customise it as shown below.
8. Click on Next button and set the deep link.
App Linking name: deep link name.
Default deep link: deep link used to open an app.
Android deep link: deep link preferentially opened on an Android device.
iOS deep Link URL: deep link preferentially opened on an iOS device.
Tip 1: Link name and Default deep link can be same and follow as https://domainname.com/xxx
Where “domainname” is URLprefix which we set above and “xxx” is specific in-app content page to re-direct.
Tip 2: You can customize the deep link, which is different from the URL prefix of the link.
Once done, click on Next button.
9. Select Set Android link behaviour for your Android application as below.
We will choose “Open in app” as we want our application to open directly as application.
Select your application from the Add Android app menu.
Redirect user to AppGallery page if the application is not installed on the device.
Tip: Ensure that the App Store ID and team ID have been configured. If they are not configured, add them as prompted.
Once done, click on Next button.
10. We have set the deep link for our news application and it will re-direct to in-app content tab (science) every time when it is shared from our share application.
To check the details and use the link we will navigate to view details
11. We will use short App link for our App Linking use case. It will re-direct the user to Science tab of news application from our shared link through different tab.
Receiving Links of App Linking
When a user is directed to the target content after tapping a received link of App Linking, your app can call the API provided by the App Linking SDK to receive the tapped link and obtain data passed through the link.
Add an Intent Filter to manifest file
Add intent filter to activity which will receive and process the link.
We will add this for Main activity for our use case.
Java:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="yourdomainname.com"
android:scheme="https"
tools:ignore="AppLinkUrlError" />
</intent-filter>
Receive the link in our Main activity
Add below code to your activity to receive and process the link.
Java:
AGConnectAppLinking.getInstance().getAppLinking(getIntent()).addOnSuccessListener(new OnSuccessListener<ResolvedLinkData>() {
@Override
public void onSuccess(ResolvedLinkData resolvedLinkData) {
System.out.println("inside button click_rcv " + resolvedLinkData.getDeepLink());
Uri deepLink1 = null;
if (resolvedLinkData != null) {
deepLink1 = resolvedLinkData.getDeepLink();
String myLink= deepLink1.toString();
if(myLink.contains("science")) {
viewPager.setCurrentItem(Constants.SCIENCE);
}
try {
Toast.makeText(MainActivity.this, deepLink1.toString(), Toast.LENGTH_SHORT).show();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e("LOG", "Exception Occured : " + e.getMessage());
e.printStackTrace();
ApiException apiException = (ApiException) e;
Log.e("LOG ", "status code " + apiException.getStatusCode());
}
});
Check Statistics for your links
Huawei App Linking provides lot many additional features to track the statistic for the created in-app links for your application which will be helpful in order to generate the analytical reports and improve the performance.
Step 1: Click on Statistics
Step 2: Check the performance graph based on the filters
Results
Tips and Tricks
1. URL prefix should be unique and can contain only lowercase letters and digits.
2. Link name and Default deep link can be same and follow as https://domainname.com/xxx
Where “domainname” is URLprefix which we set above and “xxx” is specific in-app content page to re-direct.
3. You can customize the deep link, which is different from the URL prefix of the link.
4. Ensure that the App Store ID and team ID have been configured. If they are not configured, add them as prompted.
Conclusion
This article focuses explains how in-app links can be created using Huawei App Linking capabilities and use them further for in –app content re-directions for large scale android applications. This article also explains how one can check the statistics for their app links.
Reference
https://developer.huawei.com/consum...ry-connect-Guides/agc-applinking-introduction
Audio is the soul of media, and for mobile apps in particular, it engages with users more, adds another level of immersion, and enriches content.
This is a major driver of my obsession for developing audio-related functions. In my recent post that tells how I developed a portrait retouching function for a live-streaming app, I mentioned that I wanted to create a solution that can retouch music. I know that a technology called spatial audio can help with this, and — guess what — I found a synonymous capability in HMS Core Audio Editor Kit, which can be integrated independently, or used together with other capabilities in the UI SDK of this kit.
I chose to integrate the UI SDK into my demo first, which is loaded with not only the kit's capabilities, but also a ready-to-use UI. This allows me to give the spatial audio capability a try and frees me from designing the UI. Now let's dive into the development procedure of the demo.
Development ProcedurePreparations1. Prepare the development environment, which has requirements on both software and hardware. These are:
Software requirements:
JDK version: 1.8 or later
Android Studio version: 3.X or later
minSdkVersion: 24 or later
targetSdkVersion: 33 (recommended)
compileSdkVersion: 30 (recommended)
Gradle version: 4.6 or later (recommended)
Hardware requirements: a phone running EMUI 5.0 or later, or a phone running Android whose version ranges from Android 7.0 to Android 13.
2. Configure app information in a platform called AppGallery Connect, and go through the process of registering as a developer, creating an app, generating a signing certificate fingerprint, configuring the signing certificate fingerprint, enabling the kit, and managing the default data processing location.
3. Integrate the HMS Core SDK.
4. Add necessary permissions in the AndroidManifest.xml file, including the vibration permission, microphone permission, storage write permission, storage read permission, Internet permission, network status access permission, and permission to obtaining the changed network connectivity state.
When the app's Android SDK version is 29 or later, add the following attribute to the application element, which is used for obtaining the external storage permission.
Code:
<application
android:requestLegacyExternalStorage="true"
…… >
SDK Integration1. Initialize the UI SDK and set the app authentication information. If the information is not set, this may affect some functions of the SDK.
Code:
// Obtain the API key from the agconnect-services.json file.
// It is recommended that the key be stored on cloud, which can be obtained when the app is running.
String api_key = AGConnectInstance.getInstance().getOptions().getString("client/api_key");
// Set the API key.
HAEApplication.getInstance().setApiKey(api_key);
2. Create AudioFilePickerActivity, which is a customized activity used for audio file selection.
Code:
/**
* Customized activity, used for audio file selection.
*/
public class AudioFilePickerActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
performFileSearch();
}
private void performFileSearch() {
// Select multiple audio files.
registerForActivityResult(new ActivityResultContracts.GetMultipleContents(), new ActivityResultCallback<List<Uri>>() {
@Override
public void onActivityResult(List<Uri> result) {
handleSelectedAudios(result);
finish();
}
}).launch("audio/*");
}
/**
* Process the selected audio files, turning the URIs into paths as needed.
*
* @param uriList indicates the selected audio files.
*/
private void handleSelectedAudios(List<Uri> uriList) {
// Check whether the audio files exist.
if (uriList == null || uriList.size() == 0) {
return;
}
ArrayList<String> audioList = new ArrayList<>();
for (Uri uri : uriList) {
// Obtain the real path.
String filePath = FileUtils.getRealPath(this, uri);
audioList.add(filePath);
}
// Return the audio file path to the audio editing UI.
Intent intent = new Intent();
// Use HAEConstant.AUDIO_PATH_LIST that is provided by the SDK.
intent.putExtra(HAEConstant.AUDIO_PATH_LIST, audioList);
// Use HAEConstant.RESULT_CODE as the result code.
this.setResult(HAEConstant.RESULT_CODE, intent);
finish();
}
}
The FileUtils utility class is used for obtaining the real path, which is detailed here. Below is the path to this class.
Code:
app/src/main/java/com/huawei/hms/audioeditor/demo/util/FileUtils.java
3. Add the action value to AudioFilePickerActivity in AndroidManifest.xml. The SDK would direct to a screen according to this action.
Code:
<activity
android:name=".AudioFilePickerActivity"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.hms.audioeditor.chooseaudio" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
4. Launch the audio editing screen via either:
Mode 1: Launch the screen without input parameters. In this mode, the default configurations of the SDK are used.
Code:
HAEUIManager.getInstance().launchEditorActivity(this);
{
"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"
}
Audio editing screens
Mode 2: Launch the audio editing screen with input parameters. This mode lets you set the menu list and customize the path for an output file. On top of this, the mode also allows for specifying the input audio file paths, setting the draft mode, and more.
Launch the screen with the menu list and customized output file path:
Code:
// List of level-1 menus. Below are just some examples:
ArrayList<Integer> menuList = new ArrayList<>();
// Add audio.
menuList.add(MenuCommon.MAIN_MENU_ADD_AUDIO_CODE);
// Record audio.
menuList.add(MenuCommon.MAIN_MENU_AUDIO_RECORDER_CODE);
// List of level-2 menus, which are displayed after audio files are input and selected.
ArrayList<Integer> secondMenuList = new ArrayList<>();
// Split audio.
secondMenuList.add(MenuCommon.EDIT_MENU_SPLIT_CODE);
// Delete audio.
secondMenuList.add(MenuCommon.EDIT_MENU_DEL_CODE);
// Adjust the volume.
secondMenuList.add(MenuCommon.EDIT_MENU_VOLUME2_CODE);
// Customize the output file path.
String exportPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() + "/";
AudioEditorLaunchOption.Builder audioEditorLaunch = new AudioEditorLaunchOption.Builder()
// Set the level-1 menus.
.setCustomMenuList(menuList)
// Set the level-2 menus.
.setSecondMenuList(secondMenuList)
// Set the output file path.
.setExportPath(exportPath);
// Launch the audio editing screen with the menu list and customized output file path.
try {
HAEUIManager.getInstance().launchEditorActivity(this, audioEditorLaunch.build(), new LaunchCallback() {
@Override
public void onFailed(int errCode, String errMsg) {
Toast.makeText(mContext, errMsg, Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
Level-1 menus
Level-2 menus
Launch the screen with the specified input audio file paths:
Code:
// Set the input audio file paths.
ArrayList<AudioInfo> audioInfoList = new ArrayList<>();
// Example of an audio file path:
String audioPath = "/storage/emulated/0/Music/Dream_It_Possible.flac";
// Create an instance of AudioInfo and pass the audio file path.
AudioInfo audioInfo = new AudioInfo(audioPath);
// Set the audio name.
audioInfo.setAudioName("Dream_It_Possible");
audioInfoList.add(audioInfo);
AudioEditorLaunchOption.Builder audioEditorLaunch = new AudioEditorLaunchOption.Builder()
// Set the input audio file paths.
.setFilePaths(audioInfoList);
// Launch the audio editing screen with the specified input audio file paths.
try {
HAEUIManager.getInstance().launchEditorActivity(this, audioEditorLaunch.build(), new LaunchCallback() {
@Override
public void onFailed(int errCode, String errMsg) {
Toast.makeText(mContext, errMsg, Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
Launch the screen with drafts:
Code:
// Obtain the draft list. For example:
List<DraftInfo> draftList = HAEUIManager.getInstance().getDraftList();
// Specify the first draft in the draft list.
String draftId = null;
if (!draftList.isEmpty()) {
draftId = draftList.get(0).getDraftId();
}
AudioEditorLaunchOption.Builder audioEditorLaunch = new AudioEditorLaunchOption.Builder()
// Set the draft ID, which can be null.
.setDraftId(draftId)
// Set the draft mode. NOT_SAVE is the default value, which indicates not to save a project as a draft.
.setDraftMode(AudioEditorLaunchOption.DraftMode.SAVE_DRAFT);
// Launch the audio editing screen with drafts.
try {
HAEUIManager.getInstance().launchEditorActivity(this, audioEditorLaunch.build(), new LaunchCallback() {
@Override
public void onFailed(int errCode, String errMsg) {
Toast.makeText(mContext, errMsg, Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
And just like that, SDK integration is complete, and the prototype of the audio editing app I want is ready to use.
Not bad. It has all the necessary functions of an audio editing app, and best of all, it's pretty easy to develop, thanks to the all-in-one and ready-to-use SDK.
Anyway, I tried the spatial audio function preset in the SDK and I found I could effortlessly add more width to a song. However, I also want a customized UI for my app, instead of simply using the one provided by the UI SDK. So my next step is to create a demo with the UI that I have designed and the spatial audio function.
AfterthoughtsTruth to be told, the integration process wasn't as smooth as it seemed. I encountered two issues, but luckily, after doing some of my own research and contacting the kit's technical support team, I was able to fix the issues.
The first issue I came across was that after touching the Add effects and AI dubbing buttons, the UI displayed The token has expired or is invalid, and the Android Studio console printed the HAEApplication: please set your app apiKey log. The reason for this was that the app's authentication information was not configured. There are two ways of configuring this. The first was introduced in the first step of SDK Integration of this post, while the second was to use the app's access token, which had the following code:
Code:
HAEApplication.getInstance().setAccessToken("your access token");
The second issue — which is actually another result of unconfigured app authentication information — is the Something went wrong error displayed on the screen after an operation. To solve it, first make sure that the app authentication information is configured. Once this is done, go to AppGallery Connect to check whether Audio Editor Kit has been enabled for the app. If not, enable it. Note that because of caches (of either the mobile phone or server), it may take a while before the kit works for the app.
Also, in the Preparations part, I skipped the step for configuring obfuscation scripts before adding necessary permissions. This step is, according to technical support, necessary for apps that aim to be officially released. The app I have covered in this post is just a demo, so I just skipped this step.
TakeawayNo app would be complete with audio, and with spatial audio, you can deliver an even more immersive audio experience to your users.
Developing a spatial audio function for a mobile app can be a piece of cake thanks to HMS Core Audio Editor Kit. The spatial audio capability can be integrated either independently or together with other capabilities via the UI SDK, which delivers a ready-to-use UI, so that you can skip the tricky bits and focus more on what matters to users.