How to share audio files in Android Studio (Kotlin)? - android-studio

How can I share an audio file in Android Studio? I have tried the following so far, but it doesn't work.
button.setOnLongClickListener(OnLongClickListener {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
var path = "absolute/path"
var uri = Uri.parse(path)
putExtra(Intent.EXTRA_STREAM, uri)
type = "audio/mp3"
}
startActivity(sendIntent)
true
})

Your code seems fine, but I'd suggest you to try this one:
String sharePath = Environment.getExternalStorageDirectory().getPath()
+ "/Soundboard/Ringtones/custom_ringtone.ogg"; //This is the path of your audio file
Uri uri = Uri.parse(sharePath); //Identifier of the audio file (Uniform Resource Identifier)
Intent share = new Intent(Intent.ACTION_SEND); //Create a new action_send intent
share.setType("audio/*"); //What kind of file the intent gets
share.putExtra(Intent.EXTRA_STREAM, uri); //Pass the audio file to the intent
startActivity(Intent.createChooser(share, "Share Sound File")); //Start the intent
If that doesn't work either, make sure that you granted the right permissions in the Manifest.xml file (WRITE_EXTERNAL_STORAGE):
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

I suggest you look into how to use a file provider. You'll find that you won't be able to provide the uri of the file directly as you're only allowed to expose its content uri. Have a look at the Generating the Content URI for a File section

This is worked for me
Call this method onClick Button:
fun shareAudioFile(audioFile: File) {
val uri = FileProvider.getUriForFile(applicationContext,
"com.app.package.fileprovider",
File(audioFile)
val shareIntent: Intent = ShareCompat.IntentBuilder.from(this#MainActivity)
.setType("audio/mp3")
.setStream(uri)
.intent
startActivity(Intent.createChooser(shareIntent, "Share Sound File"))
}
I replaced Uri.parse with FileProvider.getUriForFile.
Replace com.app.package with your package.
Add on Android Manifest:
<application ...>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
<application/>
applicationId is your package
Create file_paths.xml file into xml folder
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>

Share Any File as below ( Kotlin ) :
first create a folder named xml in the res folder and create a new XML Resource File named provider_paths.xml and put the below code inside it :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="."/>
<external-path
name="external_files"
path="."/>
</paths>
now go to the manifests folder and open the AndroidManifest.xml and then put the below code inside the <application> tag :
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" /> // provider_paths.xml file path in this example
</provider>
now you put the below code in the setOnLongClickListener :
button.setOnLongClickListener {
try {
val file = File("pathOfFile")
if(file.exists()) {
val uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file)
val intent = Intent(Intent.ACTION_SEND)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setType("*/*")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
toast("Error")
}
}

Related

How to get the full path of current target file using NLog at runtime in .NET Core 7?

I have an application in .NET Core Console that is working fine in .NET Core 6. I am testing the conversion of this app in the newly released .NET Core 7 and all works fine except the part where I dynamically get the path of the NLog target.
My NLog.config is like this:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="internal-CimplDataLoader.log">
<extensions>
<add assembly="NLog.Extensions.Logging" />
</extensions>
<targets>
<target xsi:type="File" name="allfile" fileName="${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}"
keepFileOpen="false"/>
</targets>
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
</rules>
</nlog>
The code that works in .NET Core 6:
if (LogManager.Configuration != null)
{
Target target = LogManager.Configuration.FindTargetByName("allfile");
var logEventInfo = new LogEventInfo { TimeStamp = DateTime.Now };
FileTarget? fileTarget;
// Unwrap the target if necessary.
if (target is not WrapperTargetBase wrapperTarget)
fileTarget = target as FileTarget;
else
fileTarget = wrapperTarget.WrappedTarget as FileTarget;
if (fileTarget != null)
{
string fileName = fileTarget.FileName.Render(logEventInfo);
string LogPath = Path.GetDirectoryName(fileName)!;
/* Work with LogPath */
}
else
{
_logger.LogError("Unable to get NLog \"allfile\" base directory.");
}
}
else
{
_logger.LogError("Unable to read NLog configuration.");
}
This same code in .NET Core 7 fails because LogManager.Configuration is always null. Is there any way I can still dynamically get the path in .NET Core 7?
NLog.config was not present, hence the failure. This was due to the switch from .NET 6 to .NET 7 and nlog.config property "Copy To Output Directory" set to "Do not copy".

Using global actions with multiple graphs in jetpack navigation

I created an application with a bottom navigation bar and added four different nav graphs using google's advanced navigation example
After that I added a fifth graph called settings that had the settings fragment along with a global action
I added an include to that graph on each of the first four graphs
when I do something like findNavController(R.id.container).navigate(SettingsDirections.showSettings()) the app crashes because it cannot find the destination or the action
but when I copy the fragment and the global action inside each of those graphs and call the above (with that graph's directions) it works
am I missing something? doesn't include actually copy everything from the other graph the original?
It seems that the include tag does not actually include all of the nav graph including the global actions
so in case someone wants to do something similar here is how I did it
first I updated the settings navigation to include a dummy action to show settings like so:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/settings"
app:startDestination="#+id/settings_dest">
<include app:graph="#navigation/questions" />
<!--this is just so safeargs will create a SettingsNavigation.showSettings it is never actually used-->
<action
android:id="#+id/show_settings"
app:destination="#id/settings_dest" />
<fragment
android:id="#+id/settings_dest"
android:name="com.example.app.ui.fragments.SettingsFragment"
android:label="#string/settings"
tools:layout="#layout/fragment_settings" >
<argument
app:argType="com.example.app.ui.model.SettingsProfile"
android:name="profile"
app:nullable="false"/>
</fragment>
</navigation>
and then on every nav graph that I wanted to use the show_settings action I would do by including this:
<include app:graph="#navigation/settings" />
<action
android:id="#+id/show_settings"
app:destination="#id/settings"
app:enterAnim="#anim/fade_in"
app:exitAnim="#anim/fade_out"
app:popEnterAnim="#anim/fade_in"
app:popExitAnim="#anim/fade_out" />
so now when I want to use directions to go to settings I do it like this
findNavController().navigate(SettingsDirections.showSettings(profile))
this will use the directions created in my settings.xml to execute the action in my current nav controller
it is important by the way the id of the action in the included file and the id of the action of the file including it to be the same
Let me show you with an example
<navigation ...
app:startDestination:"#id/nav_graph_one">
<include app:graph:"#navigation/nav_graph_one"/>
<include app:graph:"#navigation/nav_graph_two"/>
<fragment
android:id="#+id/aCommonFragment"
.../>
<action
android:id="#+id/action_grobal_aCommonFragment
app:destination="#+id/aCommonFragment"/>
</navigation>
Any where from your app you can now use
findNavController().navigate(NavGraphDirections.actionGlobalACommonFragment)
By copying nodes and actions from global actions graph to main graph in runtime it is possible to use global actions in all subgraphs and stay them in separate file.
main graph:
<navigation android:id="#+id/main_graph">
<include app:graph="#navigation/subgraph1"/>
<include app:graph="#navigation/subgraph2"/>
<navigation/>
global actions graph:
<navigation android:id="#+id/customer_dialogs">
<action android:id="#+id/actionToShareCustomer"
android:destionation="#id/shareCustomerDialog"/>
<dialog android:id="#+id/shareCustomerDialog">
<argument android:id="#+id/customerId" app:argType="long"/>
</dialog>
<navigation/>
programmatically set main_graph to navController:
override fun onCreate(savedInstanceState: Bundle?) {
with(navHostFragment.findNavController()) {
val mainGraph = navInflater.inflate(R.navigation.main_graph)
val actionsGraph = navInflater.inflate(R.navigation.customer_dialogs)
copyGraph(actionsGraph, mainGraph)
setGraph(mainGraph, null)
}
}
fun copyGraph(from: NavGraph, to: NavGraph) {
to.addAll(from)
val actions = NavDestination::class.java.getDeclaredField("actions").let {
it.isAccessible = true
it.get(from)
} as? SparseArrayCompat<NavAction>
actions?.forEach { actionId, action -> to.putAction(actionId, action) }
}
usage of action from fragment of subgraph1:
private fun shareCustomer(customerId: Long) {
findNavController().navigate(CustomerDialogsDirections.actionToShareCustomer(customerId))
}
I created an extension function when setting the default animation
fun View.navigate(id: Int, navOptions: NavOptions? = null){
var updatedNavOptions = navOptions
if(updatedNavOptions == null){
updatedNavOptions = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
}
this.findNavController().navigate(id, null, updatedNavOptions)
}
In my fragment just do so:
my_view.click { view?.navigate(R.id.how_referral_program_works) }

Is there possibility for FreeMarker to create a directory or package on Android Studio templating?

I'm trying to create a template that has a nested package.
Here's my templating code.
// root/global.xml.ftl
<?xml version="1.0"?>
<globals>
<global id="simpleLayoutName" value="${layoutName}"/>
<global id="excludeMenu" type="boolean" value="true"/>
<global id="resOut" value="${resDir}"/>
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}"/>
<#include "../common/common_globals.xml.ftl" />
</globals>
// root/recipe.xml.ftl
<?xml version="1.0"?>
<recipe>
<#if generateLayout>
<#include "../common/recipe_simple.xml.ftl" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"/>
</#if>
<instantiate from="root/src/app_package/SimpleFragment.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}Fragment.kt"/>
</recipe>
// root/template.xml
<?xml version="1.0"?>
<template
format="5"
revision="1"
name="MVVM Activity"
minApi="9"
minBuildApi="14"
description="Creates a new empty activity that uses MVVM Pattern">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="generateLayout"
name="Generate Layout File"
type="boolean"
default="true"
help="If true, a layout file will be generated" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
visibility="generateLayout"
help="The name of the layout to create for the activity" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_MVVM_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
// root/src/app_package/SimpleFragment.kt.ftl
package ${packageName}
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class ${fragmentClass}Fragment : Fragment() {
// TODO: Implement by lazy ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO: Init or setup view, viewmodel related, etc anything to setup here
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// TODO: Init or setup view, viewmodel related, etc anything to setup here
}
}
But, I didn't find any function to create a directory here: http://freemarker.apache.org/docs
Looks like I'm missing something here, is there anyone ever doing this to achieve creating a package inside output directory.
I quite catch that recipe.xml.ftl able to instantiate new file, but I still didn't try to create a directory instead, is it possible? What should add for from attribute value?
According to this article. By default, you can do this without any extra effort. Just point the new package on recipe.xml.ftl e.g:
<instantiate from="root/src/main/java/model/Simple.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/model/${modelName}.kt"/>
to element will creating the new model package inside your destination directdory.

SAP Hybris E-commerce : Send Email in CronJob

I have created a CronJob that works perfectly.
But I want to generate the sending of an email within this Cronjob. I followed a tutorial on the internet.
I start with the creation itemType of ProductsApprovedEmailProcess.
then I created productsApprovedEmailProcess to define the steps be executed by the Process Engine as follow
Then I have added an EmailContext to holds the data to be passed to the email template as follow
public class ProductsApprovedEmailContext extends CustomerEmailContext
{
private String message;
#Override
public void init(final StoreFrontCustomerProcessModel processModel, final EmailPageModel emailPageModel)
{
super.init(processModel, emailPageModel);
if (processModel instanceof ProductsApprovedEmailProcessModel)
{
setMessage(((ProductsApprovedEmailProcessModel) processModel).getMessage());
}
}
public String getMessage()
{
return message;
}
public void setMessage(final String message)
{
this.message = message;
}
}
And I had register ProductsApprovedEmailContext as a bean in Spring as follow
<bean id="productsApprovedEmailContext" class="com.hybris.training.facades.process.email.context.ProductsApprovedEmailContext"
parent="abstractEmailContext"
scope="prototype" >
</bean>
Then I created 2 Velocity templates, one for the email Subject and the other for the Body email-productsapproved-subject.vm and email-productsapproved-body.vm
And the following impex allows you to create RendererTemplates for the Subject and the Body, and attach them to an EmailPageTemplate as follow
$contentCatalog=electronicsContentCatalog
$contentCV=catalogVersion(CatalogVersion.catalog(Catalog.id[default=$contentCatalog]),CatalogVersion.version[default=Online])[default=$contentCatalog:Online]
UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
$emailResource=$config-emailResourceValue
$emailPackageName=$config-emailContextPackageName
$lang=en
INSERT_UPDATE RendererTemplate ;code[unique=true] ;contextClass ;templateScript[lang=en,translator=de.hybris.platform.commerceservices.impex.impl.FileLoaderValueTranslator];rendererType(code)[default='velocity']
;email-productsapproved-body ;$emailPackageName.ProductsApprovedEmailContext ;$emailResource/email-productsapproved-body.vm
;email-productsapproved-subject ;$emailPackageName.ProductsApprovedEmailContext ;$emailResource/email-productsapproved-subject.vm
INSERT_UPDATE EmailPage ;$contentCV[unique=true];uid[unique=true] ;masterTemplate(uid,$contentCV) ;approvalStatus(code)[default='approved']
; ;ProductApprovedEmail ;ProductApprovedEmailTemplate ;
And in the Cronjob I added this code !
final ProductsApprovedEmailProcessModel productsApprovedEmailProcessModel = (ProductsApprovedEmailProcessModel) businessProcessService
.createProcess("productsApprovedEmailProcess" + "-" + System.currentTimeMillis(), "productsApprovedEmailProcess");
productsApprovedEmailProcessModel.setMessage("Products approved in csv file");
productsApprovedEmailProcessModel.setSite(baseSiteService.getBaseSiteForUID("electronics"));
productsApprovedEmailProcessModel.setLanguage(CommerceCommonI18NService.getCurrentLanguage());
modelService.save(productsApprovedEmailProcessModel);
businessProcessService.startProcess(productsApprovedEmailProcessModel);
But a acheive this error when I'm strating CronJob using HMC Interface :
Error executing ActionNode with ID [generateProductsApprovedEmail]: HtmlTemplate associated with MasterTemplate of EmailPageModel cannot be null
UPDATE :
Here is my business process :
<process xmlns="http://www.hybris.de/xsd/processdefinition"
start="generateProductsApprovedEmail"
name="productsApprovedEmailProcess"
processClass="com.hybris.training.core.model.process.ProductsApprovedEmailProcessModel"
onError="error">
<action id="generateProductsApprovedEmail" bean="generateProductsApprovedEmail">
<transition name="OK" to="sendEmail"/>
<transition name="NOK" to="error"/>
</action>
<action id="sendEmail" bean="sendEmail">
<transition name="OK" to="removeSentEmail"/>
<transition name="NOK" to="failed"/>
</action>
<action id="removeSentEmail" bean="removeSentEmail">
<transition name="OK" to="success"/>
<transition name="NOK" to="error"/>
</action>
<end id="error" state="ERROR">Something went wrong.</end>
<end id="failed" state="FAILED">Could not send products approved in csv File email.</end>
<end id="success" state="SUCCEEDED">Sent file in email.</end>
After declaring ProductApprovedEmailTemplate (EmailPageTemplate) i got this warn and the mail is not generated :
WARN [TaskExecutor-master-264-ProcessTask [8796715713462]] [GenerateEmailAction] Could not retrieve email page model for ProductApprovedEmail and Electronics Content Catalog:Online, cannot generate email content
Look like, the blog you have followed, it has mentioned each step correctly, but you might be missed something.
Make sure you have followed the below steps correctly.
e.g.
frontendTemplateName should be matched with EmailPageTemplate one
<bean id="generateProductApprovedEmail" parent="abstractGenerateEmailAction">
<property name="frontendTemplateName" value="ProductApprovedEmail"/>
</bean>
Create Email page Template
INSERT_UPDATE EmailPageTemplate ;$contentCV[unique=true];uid[unique=true] ;active ;frontendTemplateName ;subject(code) ;htmlTemplate(code) ;restrictedPageTypes(code)
; ;ProductApprovedEmailTemplate ;true ;ProductApprovedEmail ;email-productsapproved-subject ;email-productsapproved-body ;EmailPage
Create Email Page
INSERT_UPDATE EmailPage ;$contentCV[unique=true];uid[unique=true] ;masterTemplate(uid,$contentCV);approvalStatus(code)[default='approved']
; ;ProductApprovedEmail ;ProductApprovedEmailTemplate ;

Android Plugin for unity not opening permission dialogue in unity?

I just develop android plugin for unity and i am successfully able to exchange data between unity and android plugin.
in my android plugin my code is
String SCOPE = "https://www.googleapis.com/auth/youtube";
am.getAuthToken(myAccount_[0], "oauth2:" + SCOPE, null, this,
new OnTokenAcquired(), null);
It should prompt me to allow my app to access youtube.
Its working fine for my android application but not be able to ask on unity.
here is my code of android:
public void YoutubeSubscriber() {
AccountManager am = AccountManager.get(context);
Account[] myAccount_ = AccountManager.get(context).getAccountsByType("com.google");
Log.d("==============>all", String.valueOf(myAccount_));
Log.d("==============>single", String.valueOf(myAccount_[0]));
String SCOPE = "https://www.googleapis.com/auth/youtube";
am.getAuthToken(myAccount_[0], "oauth2:" + SCOPE, null, null,
new OnTokenAcquired(), null);
Toast.makeText(this.context, myAccount_[0].toString(), Toast.LENGTH_SHORT).show();
}
and using this in unity is like that.
using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
info.text = "activity";
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
}
using (AndroidJavaClass pluginClass = new AndroidJavaClass("com.thegamecontriver.androidlib.ToastExample"))
{
if (pluginClass != null)
{
toastExample = pluginClass.CallStatic<AndroidJavaObject>("instance");
toastExample.Call("setContext", activityContext);
info.text = "After ";
toastExample.Call("YoutubeSubscriber");
/*
activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() => {
toastExample.Call("YoutubeSubscriber");
}));
*/
}
}
Note: i am able to see toast but permission is not prompting. please help
You are likely missing the permission required (USE_CREDENTIALS) to use AccountManager.
From getAuthToken SDK reference:
NOTE: If targeting your app to work on API level 22 and before,
USE_CREDENTIALS permission is needed for those platforms. See docs for
this function in API level 22.
1.Go to <UnityInstallationDirecory>\Editor\Data\PlaybackEngines\AndroidPlayer\Apk, Copy the AndroidManifest.xml file to your <ProjectName>Assets\Plugins\Android
2.Now open the copied Manifest file from <ProjectName>Assets\Plugins\Android and add <uses-permission android:name="android.permission.USE_CREDENTIALS"/> to it. Save, Build and Run. If this is a permission problem, that should now be solved.
What your AndroidManifest.xml should look like(Unity 5.4):
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<application
android:theme="#style/UnityThemeSelector"
android:icon="#drawable/app_icon"
android:label="#string/app_name"
android:debuggable="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>

Resources