From 2cf189c1b6948a4e2548fead4c23ac220db6a491 Mon Sep 17 00:00:00 2001 From: CXNT48 Date: Thu, 12 Sep 2024 17:58:07 +0200 Subject: [PATCH] KC50/TD50 Sample 1st commit --- .gitignore | 15 + .idea/compiler.xml | 6 + .idea/deploymentTargetSelector.xml | 10 + .idea/gradle.xml | 19 + .idea/migrations.xml | 10 + .idea/misc.xml | 9 + .idea/vcs.xml | 7 + LICENSE | 21 ++ README.md | 15 + app/.gitignore | 1 + app/build.gradle | 45 +++ app/proguard-rules.pro | 21 ++ app/src/main/AndroidManifest.xml | 43 +++ .../com/zebra/dualscreen/KC50Activity.java | 341 ++++++++++++++++++ .../com/zebra/dualscreen/TD50Activity.java | 328 +++++++++++++++++ .../drawable-v24/ic_launcher_foreground.xml | 30 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++ app/src/main/res/layout/activity_kc50.xml | 77 ++++ app/src/main/res/layout/activity_td50.xml | 71 ++++ app/src/main/res/menu/menu_main.xml | 25 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/values-night/themes.xml | 7 + app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 10 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + build.gradle | 5 + gradle.properties | 21 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 +++++++++++++ gradlew.bat | 92 +++++ settings.gradle | 16 + 45 files changed, 1723 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/zebra/dualscreen/KC50Activity.java create mode 100644 app/src/main/java/com/zebra/dualscreen/TD50Activity.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/layout/activity_kc50.xml create mode 100644 app/src/main/res/layout/activity_td50.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..8306744 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..28d2bd1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 { N.DZL } + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b355a6a --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +## SAMPLE CODE FOR THE KC50/TD50 KIOSK SOLUTION + +Sample code showing how to use some Large Screens APIs from Android and how to split an application across multiple screens. + +### Basic functionalities +- Open an activity on the secondary screen (TD50) +- If the secondary screen is not available, the activity will be opened on the main screen (KC50) and when when the secondary screen is available, the activity will be moved to the secondary screen. +- The application will be split across the two screens. +- Each screen is showing a information specific to the display is it shown on. +- Each screen holds some colored buttons to show how messages can be sent between the two screens. + +### Device set up to run this sample code +- Ensure you have both the KC50 and TD50 devices available and connected. + +*Please be aware that this application / sample is provided as-is for demonstration purposes without any guarantee of support* \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..19f9b83 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.application' +} + + + +android { + namespace 'com.zebra.dualscreen' + compileSdk 34 + + buildFeatures { + // Enables Jetpack Compose for this module + //compose true + //aidl true + } + + defaultConfig { + applicationId "com.zebra.kc50_td50" + minSdk 30 + targetSdk 34 + versionCode 1 + versionName "1.0" + archivesBaseName = "$applicationId-v$versionName" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildToolsVersion '34.0.0' + +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2f91cbc --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/zebra/dualscreen/KC50Activity.java b/app/src/main/java/com/zebra/dualscreen/KC50Activity.java new file mode 100644 index 0000000..c5943b4 --- /dev/null +++ b/app/src/main/java/com/zebra/dualscreen/KC50Activity.java @@ -0,0 +1,341 @@ +package com.zebra.dualscreen; + +import static android.provider.ContactsContract.Directory.PACKAGE_NAME; + +import androidx.appcompat.app.AppCompatActivity; + +import android.app.ActivityOptions; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.hardware.display.DisplayManager; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.method.ScrollingMovementMethod; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.View; +import android.view.WindowMetrics; +import android.widget.TextView; + + +import java.util.List; +import java.util.Random; + +/* +* ZEBRA WORKSTATION CONNECT EXERCISER - +* */ +public class KC50Activity extends AppCompatActivity { + + private final static String TAG1 = "LIFECYCLE"; + String last_activity_state ="N/A"; + TextView tvOut; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_kc50); + + tvOut = findViewById(R.id.txtOut); + tvOut.setMovementMethod(new ScrollingMovementMethod()); + //tvOut.setText( largeScreenInfo() ); + printAppendOnScreen( currentScreenInfo() ); + + Log.i(TAG1, "onCreate"); + last_activity_state = "onCreate"; + + printAppendOnScreen( getDisplayInfo() ); + + registerReceiver(dualScreenReceiver, new IntentFilter("com.zebra.dualscreen.TD50_ACTION")); + } + + @Override + protected void onStart() { + super.onStart(); + Log.i(TAG1, "onStart"); + last_activity_state = "onStart"; + } + + @Override + protected void onResume() { + super.onResume(); + Log.i(TAG1, "onResume"); + last_activity_state = "onResume"; + } + + @Override + protected void onPause() { + super.onPause(); + Log.i(TAG1, "onPause"); + last_activity_state = "onPause"; + } + + @Override + protected void onStop() { + super.onStop(); + Log.i(TAG1, "onStop"); + last_activity_state = "onStop"; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.i(TAG1, "onDestroy"); + last_activity_state = "onDestroy"; + } + + + + + + public void btClick_HDLauncher(View v) { + //Launch on 2nd display if available + ActivityOptions ao = ActivityOptions.makeBasic(); + int other_display_id = 0; + int cur_display_id = getDisplay().getDisplayId(); + if(cur_display_id>0){ + other_display_id = cur_display_id; + } else { + DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + Display[] displays = displayManager.getDisplays(); + for (Display _d : displays) { + if (_d.getDisplayId() >0 ) { + other_display_id = _d.getDisplayId(); + break; + } + } + } + + ao.setLaunchDisplayId(other_display_id); + + Bundle bao = ao.toBundle(); + Intent intent= new Intent(getBaseContext(), TD50Activity.class); + intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent, bao); + } + + + + String currentScreenInfo(){ + StringBuilder _sb = new StringBuilder(); + + DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + Display[] displays = displayManager.getDisplays(); + int currentDisplayId = getDisplay().getDisplayId(); + for (Display _d:displays) { + + if( _d.getDisplayId() != currentDisplayId ) + continue; + + _sb.append( "DISPLAY ID="+_d.getDisplayId()+" " ) ; + DisplayMetrics metrics = new DisplayMetrics(); + _d.getRealMetrics(metrics); + + String DISPMETRICS_DENSITY= ""+metrics.density; + String DISPMETRICS_DENSITY_DPI= ""+metrics.densityDpi; + String DISPMETRICS_SCALED_DENSITY= ""+metrics.scaledDensity; + String DISPMETRICS_X_DPI= ""+metrics.xdpi; + String DISPMETRICS_Y_DPI= ""+metrics.ydpi; + String DISPMETRICS_H_PIXEL= ""+metrics.heightPixels; + String DISPMETRICS_W_PIXEL= ""+metrics.widthPixels; + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + _sb.append( _d.getDeviceProductInfo().getProductId()+"\n" ) ; + String PROD_ID=_d.getDeviceProductInfo().getProductId(); + String PROD_INFO=_d.getDeviceProductInfo().getName(); + String PROD_MANUF_PNPID=_d.getDeviceProductInfo().getManufacturerPnpId(); + String PROD_SINKTYPE=""+_d.getDeviceProductInfo().getConnectionToSinkType(); + String PROD_YEARWEEK=""+_d.getDeviceProductInfo().getManufactureYear()+"-"+_d.getDeviceProductInfo().getManufactureWeek(); + String PROD_MODELYEAR=""+_d.getDeviceProductInfo().getModelYear(); + _sb.append( "NAME="+_d.getName()+" " ) ; + _sb.append( "PROD_ID="+ PROD_ID +" " ) ; + _sb.append( "PROD_INFO="+ PROD_INFO +" " ) ; + _sb.append( "PROD_MANUF_PNPID="+ PROD_MANUF_PNPID +" " ) ; + _sb.append( "PROD_SINKTYPE="+ PROD_SINKTYPE +" " ) ; + _sb.append( "PROD_YEARWEEK="+ PROD_YEARWEEK +" " ) ; + _sb.append( "PROD_MODELYEAR="+ PROD_MODELYEAR +" " ) ; + } + } catch (Exception e) { + _sb.append( "NO PRODUCT INFO AVAILABLE.") ; + } + _sb.append("\n"); + + /* + DisplayMetrics metrics = getResources().getDisplayMetrics(); + String DISPMETRICS_DENSITY= ""+metrics.density; + String DISPMETRICS_DENSITY_DPI= ""+metrics.densityDpi; + String DISPMETRICS_SCALED_DENSITY= ""+metrics.scaledDensity; + String DISPMETRICS_X_DPI= ""+metrics.xdpi; + String DISPMETRICS_Y_DPI= ""+metrics.ydpi; + String DISPMETRICS_H_PIXEL= ""+metrics.heightPixels; + String DISPMETRICS_W_PIXEL= ""+metrics.widthPixels; + + */ + + + _sb.append( "DISPMETRICS_DENSITY="+ DISPMETRICS_DENSITY +" " ) ; + _sb.append( "DISPMETRICS_DENSITY_DPI="+ DISPMETRICS_DENSITY_DPI +" " ) ; + _sb.append( "DISPMETRICS_SCALED_DENSITY="+ DISPMETRICS_SCALED_DENSITY +" " ) ; + _sb.append( "DISPMETRICS_X_DPI="+ DISPMETRICS_X_DPI +" " ) ; + _sb.append( "DISPMETRICS_Y_DPI="+ DISPMETRICS_Y_DPI +" " ) ; + _sb.append( "DISPMETRICS_H_PIXEL="+ DISPMETRICS_H_PIXEL +" " ) ; + _sb.append( "DISPMETRICS_W_PIXEL="+ DISPMETRICS_W_PIXEL +" " ) ; + _sb.append("\n"); + } + + Display activityDisplay = getDisplay(); + _sb.append( "\nACTIVITY DISPLAY ID="+activityDisplay.getDisplayId()+"\n" ) ; + + _sb.append("\n"); + WindowMetrics maximumWindowMetrics = getWindowManager().getMaximumWindowMetrics(); + WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics(); + + _sb.append( "METRICS MAX="+maximumWindowMetrics.getBounds().width()+"x"+maximumWindowMetrics.getBounds().height()+"\n" ) ; + _sb.append( "METRICS CURRENT="+windowMetrics.getBounds().width()+"x"+windowMetrics.getBounds().height()+"\n" ) ; + _sb.append("\n"); + + + return _sb.toString(); + } + + + boolean _IS_TOP_RESUMED_ACTIVITY=false; + @Override + public void onTopResumedActivityChanged(boolean topResumed) { + if (topResumed) { + _IS_TOP_RESUMED_ACTIVITY = true; + } else { + _IS_TOP_RESUMED_ACTIVITY = false; + } + + tvOut.setText( currentScreenInfo() ); + printAppendOnScreen( getDisplayInfo() ); + + } + + private String getAppPackageName() { + String appPackageName = ""; + PackageManager pm = this.getPackageManager(); + + final Intent secondaryIntent = new Intent(Intent.ACTION_MAIN, null); + secondaryIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME); + final List appsList = pm.queryIntentActivities(secondaryIntent, 0); + + for (ResolveInfo resolveInfo : appsList) { + if (resolveInfo.activityInfo.packageName.contains(PACKAGE_NAME)) { + appPackageName = resolveInfo.activityInfo.packageName; + } + } + return appPackageName; + } + + void printAppendOnScreen(String txt ){ + runOnUiThread(new Runnable() { + @Override + public void run() { + tvOut.setText( tvOut.getText() +"\n"+ txt ); + } + }); + } + + + String getDisplayInfo(){ + Display activityDisplay = getDisplay(); + + return "ACTIVITY RUNNING ON DISPLAY ID="+activityDisplay.getDisplayId()+"\n" ; + } + + public void onClickbtn_RED(View v) { + sendMessageToTD50("RED"); + } + public void onClickbtn_GREEN(View v) { + sendMessageToTD50("GREEN"); + } + public void onClickbtn_BLUE(View v) { + sendMessageToTD50("BLUE"); + } + + void sendMessageToTD50(String msg){ + Intent intent = new Intent(); + intent.setAction("com.zebra.dualscreen.KC50_ACTION"); + intent.putExtra("msg", msg); + sendBroadcast(intent); + } + + + + private BroadcastReceiver dualScreenReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals("com.zebra.dualscreen.TD50_ACTION")) { + String message = intent.getStringExtra("msg"); + Log.i("KC50", "Received message from TD50: " + message); + printAppendOnScreen( "Received message from TD50: " + message ); + + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + Random random = new Random(); + for (int i = 0; i < 4; i++) { + int randomValue = 50 + random.nextInt(451); // 451 because upper bound is exclusive + playPCMData(generateNote((double) randomValue, 0.3, 1000)); + } + } + }); + } + } + }; + + + public void playPCMData(byte[] data) { + try { + AudioTrack audioTrack = new AudioTrack( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(), + new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_8BIT) + .setSampleRate(11025) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) + .build(), + data.length, + AudioTrack.MODE_STATIC, + AudioManager.AUDIO_SESSION_ID_GENERATE + ); + + audioTrack.write(data, 0, data.length); + audioTrack.play(); + } catch (Exception e) { + // Handle exception + } + } + + + public byte[] generateNote(double frequency, double durationInSeconds, int sampleRate) { + int numSamples = (int) (durationInSeconds * sampleRate); + byte[] samples = new byte[numSamples]; + + for (int i = 0; i < numSamples; i++) { + double time = i / 100.0; + samples[i] = (byte) ((Math.sin(frequency * time) * 127.0) + 0); + } + + return samples; + } + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/zebra/dualscreen/TD50Activity.java b/app/src/main/java/com/zebra/dualscreen/TD50Activity.java new file mode 100644 index 0000000..04c8657 --- /dev/null +++ b/app/src/main/java/com/zebra/dualscreen/TD50Activity.java @@ -0,0 +1,328 @@ +package com.zebra.dualscreen; + +import static android.widget.Toast.makeText; + +import androidx.appcompat.app.AppCompatActivity; + +import android.app.ActivityOptions; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.hardware.display.DisplayManager; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.View; +import android.view.WindowMetrics; +import android.widget.TextView; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Random; +//https://developer.android.com/reference/android/hardware/display/DisplayManager#registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener,%20android.os.Handler) + + + +//HAD TO SET org.gradle.jvmargs=-Xmx8G TO BUILD.... +public class TD50Activity extends AppCompatActivity { + + Intent starterIntent; + TextView tvOut; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + starterIntent = getIntent(); + + setContentView(R.layout.activity_td50); + DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + displayManager.registerDisplayListener(new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { //As a multi-screen-aware app, detect a 2nd screen when available + //move this launcher to the 2nd screen when available + finish(); + ActivityOptions ao =ActivityOptions.makeBasic(); + ao.setLaunchDisplayId(displayId); + Bundle bao = ao.toBundle(); + starterIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(starterIntent, bao); + } + + @Override + public void onDisplayRemoved(int displayId) { + finish(); + ActivityOptions ao =ActivityOptions.makeBasic(); + ao.setLaunchDisplayId(0); + Bundle bao = ao.toBundle(); + starterIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(starterIntent, bao); + } + + @Override + public void onDisplayChanged(int displayId) { + recreate(); + } + }, null); + + tvOut = findViewById(R.id.textView); + + printAppendOnScreen( currentScreenInfo() ); + + registerReceiver(dualScreenReceiver, new IntentFilter("com.zebra.dualscreen.KC50_ACTION")); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + public void onClickbtn_CYAN(View v) { + sendMessageToKC50("CYAN"); + } + public void onClickbtn_MAGENTA(View v) { + sendMessageToKC50("MAGENTA"); + } + public void onClickbtn_YELLOW(View v) { + sendMessageToKC50("YELLOW"); + } + + void sendMessageToKC50(String msg){ + Intent intent = new Intent(); + intent.setAction("com.zebra.dualscreen.TD50_ACTION"); + intent.putExtra("msg", msg); + sendBroadcast(intent); + } + + + void printAppendOnScreen(String txt ){ + runOnUiThread(new Runnable() { + @Override + public void run() { + tvOut.setText( tvOut.getText() +"\n"+ txt ); + } + }); + } + + + String getDisplayInfo(){ + Display activityDisplay = getDisplay(); + + return "ACTIVITY RUNNING ON DISPLAY ID="+activityDisplay.getDisplayId()+"\n" ; + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + + } + + @Override + protected void onStart() { + super.onStart(); + + } + + + + public String loadJSONFromAsset(String jsonAssetFileName) { + String json = null; + try { + InputStream is = this.getAssets().open(jsonAssetFileName); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + json = new String(buffer, "UTF-8"); + } catch (IOException ex) { + ex.printStackTrace(); + return null; + } + return json; + } + + + private void saveStringToLocalFile(String srcFile, String jsonAsset) { + try { + File f = new File(srcFile); + if (f.exists()) { + f.delete(); + } + + f.createNewFile(); + Process _p = Runtime.getRuntime().exec("chmod 666 " + srcFile); //chmod needed for /enterprise + _p.waitFor(); + Log.i("TD50", "chmod 666 result="+_p.exitValue()); + + FileOutputStream fos = new FileOutputStream(f); + fos.write( jsonAsset.getBytes(StandardCharsets.UTF_8) ); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + String currentScreenInfo(){ + StringBuilder _sb = new StringBuilder(); + + DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + Display[] displays = displayManager.getDisplays(); + int currentDisplayId = getDisplay().getDisplayId(); + for (Display _d:displays) { + + if( _d.getDisplayId() != currentDisplayId ) + continue; + + _sb.append( "DISPLAY ID="+_d.getDisplayId()+" " ) ; + DisplayMetrics metrics = new DisplayMetrics(); + _d.getRealMetrics(metrics); + + String DISPMETRICS_DENSITY= ""+metrics.density; + String DISPMETRICS_DENSITY_DPI= ""+metrics.densityDpi; + String DISPMETRICS_SCALED_DENSITY= ""+metrics.scaledDensity; + String DISPMETRICS_X_DPI= ""+metrics.xdpi; + String DISPMETRICS_Y_DPI= ""+metrics.ydpi; + String DISPMETRICS_H_PIXEL= ""+metrics.heightPixels; + String DISPMETRICS_W_PIXEL= ""+metrics.widthPixels; + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + _sb.append( _d.getDeviceProductInfo().getProductId()+"\n" ) ; + String PROD_ID=_d.getDeviceProductInfo().getProductId(); + String PROD_INFO=_d.getDeviceProductInfo().getName(); + String PROD_MANUF_PNPID=_d.getDeviceProductInfo().getManufacturerPnpId(); + String PROD_SINKTYPE=""+_d.getDeviceProductInfo().getConnectionToSinkType(); + String PROD_YEARWEEK=""+_d.getDeviceProductInfo().getManufactureYear()+"-"+_d.getDeviceProductInfo().getManufactureWeek(); + String PROD_MODELYEAR=""+_d.getDeviceProductInfo().getModelYear(); + _sb.append( "NAME="+_d.getName()+" " ) ; + _sb.append( "PROD_ID="+ PROD_ID +" " ) ; + _sb.append( "PROD_INFO="+ PROD_INFO +" " ) ; + _sb.append( "PROD_MANUF_PNPID="+ PROD_MANUF_PNPID +" " ) ; + _sb.append( "PROD_SINKTYPE="+ PROD_SINKTYPE +" " ) ; + _sb.append( "PROD_YEARWEEK="+ PROD_YEARWEEK +" " ) ; + _sb.append( "PROD_MODELYEAR="+ PROD_MODELYEAR +" " ) ; + } + } catch (Exception e) { + _sb.append( "NO PRODUCT INFO AVAILABLE.") ; + } + _sb.append("\n"); + + /* + DisplayMetrics metrics = getResources().getDisplayMetrics(); + String DISPMETRICS_DENSITY= ""+metrics.density; + String DISPMETRICS_DENSITY_DPI= ""+metrics.densityDpi; + String DISPMETRICS_SCALED_DENSITY= ""+metrics.scaledDensity; + String DISPMETRICS_X_DPI= ""+metrics.xdpi; + String DISPMETRICS_Y_DPI= ""+metrics.ydpi; + String DISPMETRICS_H_PIXEL= ""+metrics.heightPixels; + String DISPMETRICS_W_PIXEL= ""+metrics.widthPixels; + + */ + + + _sb.append( "DISPMETRICS_DENSITY="+ DISPMETRICS_DENSITY +" " ) ; + _sb.append( "DISPMETRICS_DENSITY_DPI="+ DISPMETRICS_DENSITY_DPI +" " ) ; + _sb.append( "DISPMETRICS_SCALED_DENSITY="+ DISPMETRICS_SCALED_DENSITY +" " ) ; + _sb.append( "DISPMETRICS_X_DPI="+ DISPMETRICS_X_DPI +" " ) ; + _sb.append( "DISPMETRICS_Y_DPI="+ DISPMETRICS_Y_DPI +" " ) ; + _sb.append( "DISPMETRICS_H_PIXEL="+ DISPMETRICS_H_PIXEL +" " ) ; + _sb.append( "DISPMETRICS_W_PIXEL="+ DISPMETRICS_W_PIXEL +" " ) ; + _sb.append("\n"); + } + + Display activityDisplay = getDisplay(); + _sb.append( "\nACTIVITY DISPLAY ID="+activityDisplay.getDisplayId()+"\n" ) ; + + _sb.append("\n"); + WindowMetrics maximumWindowMetrics = getWindowManager().getMaximumWindowMetrics(); + WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics(); + + _sb.append( "METRICS MAX="+maximumWindowMetrics.getBounds().width()+"x"+maximumWindowMetrics.getBounds().height()+"\n" ) ; + _sb.append( "METRICS CURRENT="+windowMetrics.getBounds().width()+"x"+windowMetrics.getBounds().height()+"\n" ) ; + _sb.append("\n"); + + + return _sb.toString(); + } + + + private BroadcastReceiver dualScreenReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals("com.zebra.dualscreen.KC50_ACTION")) { + String message = intent.getStringExtra("msg"); + Log.i("TD50", "Received message from KC50: " + message); + printAppendOnScreen( "Received message from KC50: " + message ); + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + Random random = new Random(); + for (int i = 0; i < 4; i++) { + int randomValue = 50 + random.nextInt(451); // 451 because upper bound is exclusive + playPCMData(generateNote((double) randomValue, 0.3, 1000)); + } + } + }); + + } + } + }; + + + public void playPCMData(byte[] data) { + try { + AudioTrack audioTrack = new AudioTrack( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(), + new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_8BIT) + .setSampleRate(11025) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) + .build(), + data.length, + AudioTrack.MODE_STATIC, + AudioManager.AUDIO_SESSION_ID_GENERATE + ); + + audioTrack.write(data, 0, data.length); + audioTrack.play(); + } catch (Exception e) { + // Handle exception + } + } + + + public byte[] generateNote(double frequency, double durationInSeconds, int sampleRate) { + int numSamples = (int) (durationInSeconds * sampleRate); + byte[] samples = new byte[numSamples]; + + for (int i = 0; i < numSamples; i++) { + double time = i / 100.0; + samples[i] = (byte) ((Math.sin(frequency * time) * 127.0) + 0); + } + + return samples; + } + + + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_kc50.xml b/app/src/main/res/layout/activity_kc50.xml new file mode 100644 index 0000000..c142911 --- /dev/null +++ b/app/src/main/res/layout/activity_kc50.xml @@ -0,0 +1,77 @@ + + + + + + + + + + +