Diagnostic Tool Investigation - Nokia Lumia 800

Hello Folks,
some time ago I did some examination on the diagnostics tool included with every firmware. I did not make any siginficant progress because most of the things I tried reqiuired certificates and signed libraries. However I want to share my thoughts so maybe someone else is able to do smoething with this tool..
The main "vulnerability" in the diagnostics tool is that it tries to load every DLL that is defined as AssemblyPart. Here is the MainPage Constructor
Code:
public MainPage()
{
base.\u002Ector();
this.InitializeComponent();
this.oddtMenuItems = new List<OddtMenuItem>();
AssemblyPartCollection parts = Deployment.Current.Parts;
RadioTechnology radio = this.GetRadio();
bool flag = this.isLTEnabled();
foreach (AssemblyPart assemblyPart in (PresentationFrameworkCollection<AssemblyPart>) parts)
{
try
{
Assembly assembly = Assembly.Load(assemblyPart.Source.Replace(".dll", string.Empty));
OddtMenuItem oddtMenuItem = (OddtMenuItem) Enumerable.FirstOrDefault<object>((IEnumerable<object>) assembly.GetCustomAttributes(typeof (OddtMenuItem), true));
if (oddtMenuItem != null)
{
if (!assembly.Equals((object) Assembly.GetExecutingAssembly()))
{
if (oddtMenuItem.RadioType != RadioTechnology.NA && (oddtMenuItem.RadioType & radio) == RadioTechnology.NA)
oddtMenuItem.setEnabled(false);
if (oddtMenuItem.Title == "Wireless LAN" || oddtMenuItem.Title == "APN Settings")
{
oddtMenuItem.setEnabled(false);
this.oddtMenuItems.Remove(oddtMenuItem);
}
if (oddtMenuItem.Title == "Life Timer" && !flag)
{
oddtMenuItem.setEnabled(false);
this.oddtMenuItems.Remove(oddtMenuItem);
}
this.oddtMenuItems.Add(oddtMenuItem);
}
}
}
catch (Exception ex)
{
}
}
this.PrioritizeMenuItems();
this.MainListBox.DataContext = (object) this.oddtMenuItems;
if ((bool) Application.Current.Resources[(object) "OddtDisclaimerAccepted"])
{
this.ContentGrid.Visibility = Visibility.Visible;
this.DisclaimerGrid.Visibility = Visibility.Collapsed;
this.get_ApplicationBar().set_IsVisible(true);
}
else
{
this.ContentGrid.Visibility = Visibility.Collapsed;
this.DisclaimerGrid.Visibility = Visibility.Visible;
this.get_ApplicationBar().set_IsVisible(false);
}
}
The AssemblyParts are defined in the WMAppManifest.xaml:
Code:
<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="OddtApplication" EntryPointType="OddtApplication.App" RuntimeVersion="4.7.50308.0">
<Deployment.Parts>
<AssemblyPart x:Name="OddtApplication" Source="OddtApplication.dll" />
<AssemblyPart x:Name="About" Source="About.dll" />
<AssemblyPart x:Name="Accelerometer" Source="Accelerometer.dll" />
<AssemblyPart x:Name="Als" Source="Als.dll" />
<AssemblyPart x:Name="APN" Source="APN.dll" />
<AssemblyPart x:Name="AudioLoopback" Source="AudioLoopback.dll" />
<AssemblyPart x:Name="Battery" Source="Battery.dll" />
<AssemblyPart x:Name="Bluetooth" Source="Bluetooth.dll" />
<AssemblyPart x:Name="Camera" Source="Camera.dll" />
<AssemblyPart x:Name="CareBattery" Source="CareBattery.dll" />
<AssemblyPart x:Name="CareTestSequence" Source="CareTestSequence.dll" />
<AssemblyPart x:Name="Dtmf" Source="Dtmf.dll" />
<AssemblyPart x:Name="ExampleLibrary" Source="ExampleLibrary.dll" />
<AssemblyPart x:Name="Gyroscope" Source="Gyroscope.dll" />
<AssemblyPart x:Name="HardwareButtons" Source="HardwareButtons.dll" />
<AssemblyPart x:Name="Headset" Source="Headset.dll" />
<AssemblyPart x:Name="LcdWhite" Source="LcdWhite.dll" />
<AssemblyPart x:Name="LedKeypad" Source="LedKeypad.dll" />
<AssemblyPart x:Name="LifeTimer" Source="LifeTimer.dll" />
<AssemblyPart x:Name="LTEBandLock" Source="LTEBandLock.dll" />
<AssemblyPart x:Name="LTETx" Source="LTETx.dll" />
<AssemblyPart x:Name="Magnetometer" Source="Magnetometer.dll" />
<AssemblyPart x:Name="Memory" Source="Memory.dll" />
<AssemblyPart x:Name="Microsoft.Phone.Controls" Source="Microsoft.Phone.Controls.dll" />
<AssemblyPart x:Name="Microsoft.Phone.Controls.Toolkit" Source="Microsoft.Phone.Controls.Toolkit.dll" />
<AssemblyPart x:Name="MMS" Source="MMS.dll" />
<AssemblyPart x:Name="NonLinearNavigationService" Source="NonLinearNavigationService.dll" />
<AssemblyPart x:Name="OddtAttributes" Source="OddtAttributes.dll" />
<AssemblyPart x:Name="PowerSource" Source="PowerSource.dll" />
<AssemblyPart x:Name="Proximity" Source="Proximity.dll" />
<AssemblyPart x:Name="RadioAccess" Source="RadioAccess.dll" />
<AssemblyPart x:Name="Settings" Source="Settings.dll" />
<AssemblyPart x:Name="Speaker" Source="Speaker.dll" />
<AssemblyPart x:Name="Touch" Source="Touch.dll" />
<AssemblyPart x:Name="Vibra" Source="Vibra.dll" />
<AssemblyPart x:Name="WirelessLAN" Source="WirelessLAN.dll" />
<AssemblyPart x:Name="Microsoft.Phone.Media.Extended" Source="Microsoft.Phone.Media.Extended.dll" />
</Deployment.Parts>
</Deployment>
For an MenuItem to appear in the diagnostics app it has to be flagged with the OddtMenuItem-Attribute (or however you call this).
Code:
[AttributeUsage(AttributeTargets.Assembly)]
public class OddtMenuItem : Attribute
{
private OddtMenuItemStatus status;
private string title;
private string description;
private Uri entryPage;
private int priority;
private RadioTechnology radiotype;
public Brush ForegroundColor
{
get
{
if (this.status == OddtMenuItemStatus.Alpha)
return (Brush) new SolidColorBrush(Colors.Gray);
if ((Color) ((FrameworkElement) new PhoneApplicationPage()).Resources[(object) "PhoneBackgroundColor"] == Color.FromArgb(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue))
return (Brush) new SolidColorBrush(Colors.Black);
else
return (Brush) new SolidColorBrush(Colors.White);
}
}
public bool IsEnabled
{
get
{
return this.status > OddtMenuItemStatus.Alpha;
}
}
public RadioTechnology RadioType
{
get
{
return this.radiotype;
}
}
public Visibility Visibility
{
get
{
return this.status == (OddtMenuItemStatus.Alpha | OddtMenuItemStatus.Beta) ? Visibility.Visible : Visibility.Collapsed;
}
}
public string Title
{
get
{
return this.title;
}
}
public string Description
{
get
{
return this.description;
}
}
public Uri EntryPage
{
get
{
return this.entryPage;
}
}
public int Priority
{
get
{
return this.priority;
}
set
{
this.priority = value;
}
}
public OddtMenuItem(OddtMenuItemStatus status, string title, string description, string location, RadioTechnology radiotype = RadioTechnology.NA)
{
this.status = status;
this.title = status != OddtMenuItemStatus.Beta ? title : string.Format("{{{0}}}", (object) title);
this.description = description;
this.entryPage = new Uri(location, UriKind.Relative);
this.priority = 99999;
this.radiotype = radiotype;
}
public void setEnabled(bool enable)
{
if (enable)
return;
this.status = OddtMenuItemStatus.Alpha;
}
}
I think that we can execute code with high Level privileges as soon as we are able to manipulate the WMAppManifest.xaml and copy a DLL into the App folder. The main Problem is that every Manipulation of the Folder or one of ist files invalidates the signature.
Regards
Chris

Related

[Q] Help with Android app development

Hello there
i'm trying to make an app that change the ringer profile when receiving a specific sms
i've reached here and i tested the app on the emulator and it's working only with the buttons
here's my codes
MainActivity.java
Code:
import android.media.AudioManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button Vibrate , Ring , Silent , Mode;
private TextView Status;
private static AudioManager myAudioManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Vibrate = (Button)findViewById(R.id.button2);
Ring = (Button)findViewById(R.id.button4);
Silent = (Button)findViewById(R.id.button3);
Mode = (Button)findViewById(R.id.button1);
Status = (TextView)findViewById(R.id.textView2);
myAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
}
public static void vibrate(View view){
myAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
}
public static void ring(View view){
myAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
public static void silent(View view){
myAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
}
public void mode(View view){
int mod = myAudioManager.getRingerMode();
if(mod == AudioManager.RINGER_MODE_NORMAL){
Status.setText("Current Status: Ring");
}
else if(mod == AudioManager.RINGER_MODE_SILENT){
Status.setText("Current Status: Silent");
}
else if(mod == AudioManager.RINGER_MODE_VIBRATE){
Status.setText("Current Status: Vibrate");
}
else{
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
RecieveSMS.java
Code:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import android.view.View;
public class RecieveSMS extends BroadcastReceiver
{
private static final String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
private Context mContext;
private Intent mIntent;
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
mIntent = intent;
String action = intent.getAction();
if(action.equals(ACTION_SMS_RECEIVED)){
String str = "";
View v = null;
/* Get all messages contained in the Intent*/
SmsMessage[] msgs = getMessagesFromIntent(mIntent);
if (msgs != null) {
for (int i = 0; i < msgs.length; i++) {
str += msgs[i].getMessageBody().toString();
}
if(str=="ring")
MainActivity.ring(v);
else if (str=="vibrate")
MainActivity.vibrate(v);
else if(str=="silent")
MainActivity.silent(v);
}
// ---send a broadcast intent to update the SMS received in the
// activity---
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("SMS_RECEIVED_ACTION");
context.sendBroadcast(broadcastIntent);
}
}
public static SmsMessage[] getMessagesFromIntent(Intent intent) {
Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
byte[][] pduObjs = new byte[messages.length][];
for (int i = 0; i < messages.length; i++) {
pduObjs[i] = (byte[]) messages[i];
}
byte[][] pdus = new byte[pduObjs.length][];
int pduCount = pdus.length;
SmsMessage[] msgs = new SmsMessage[pduCount];
for (int i = 0; i < pduCount; i++) {
pdus[i] = pduObjs[i];
msgs[i] = SmsMessage.createFromPdu(pdus[i]);
}
return msgs;
}
activity_main.xml
Code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:text="@string/audio"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginBottom="144dp"
android:layout_marginLeft="40dp"
android:layout_toLeftOf="@+id/button2"
android:onClick="silent"
android:text="@string/Silent" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_toRightOf="@+id/button1"
android:onClick="ring"
android:text="@string/Ring" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/button2"
android:layout_alignLeft="@+id/button3"
android:layout_marginBottom="15dp"
android:onClick="mode"
android:text="@string/Mode" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="46dp"
android:text="@string/Status"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button3"
android:layout_alignBottom="@+id/button3"
android:layout_alignRight="@+id/textView1"
android:onClick="vibrate"
android:text="@string/Vibrate" />
</RelativeLayout>
strings.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Test</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="audio">Set Audio Profiles</string>
<string name="Ring">Ring</string>
<string name="Vibrate">Vibrate</string>
<string name="Silent">Silent</string>
<string name="Mode">Current Mode</string>
<string name="Status">Current Status</string>
</resources>
manifest.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.test"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="22" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.test.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".RecieveSMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
</manifest>
can anyone help me please ??

Example of Bank Card Recognition and Payment Screen - ML KIT

Bank Card Recognition
The bank card recognition service can quickly recognize information such as the bank card number, covering mainstream bank cards such as China UnionPay, American Express, Mastercard, Visa, and JCB around the world. It is widely used in finance and payment scenarios requiring bank card binding to quickly extract bank card information, realizing quick input of bank card information.
{
"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"
}
Configuring App Information in AppGallery Connect
Before you start developing an app, configure app information in AppGallery Connect.
Registering as a Developer
Before you get started, you must register as a Huawei developer and complete identity verification on HUAWEI Developers. For details refer to Registration and Verification.
Creating an App
Follow the instructions to create an app in Creating an AppGallery Connect Project and Adding an App to the Project.
Enabling the Service
1. Sign in to AppGallery Connect and select My projects.
2. Find your project from the project list and click the app for which you need to enable a service on the project card.
3. Click the Manage APIs tab and toggle the switch for the service to be enabled.
Adding the AppGallery Connect Configuration File
For adding configuration file refer this below link
https://developer.huawei.com/consum...MSCore-Guides/add-appgallery-0000001050038080
Configuring the Maven Repository Address
For adding Maven files refer this below link
https://developer.huawei.com/consum.../HMSCore-Guides/config-maven-0000001050040031
Integrating the Bank Card Recognition Plug-in
The bank card recognition service supports two SDK integration modes: full SDK and base SDK. You can select either one based on your needs.
Mode 1: Full SDK Integration (Recommended)
Combine the bank card recognition model and bank card recognition plug-in into a package.
The following is the sample code for integration in full SDK mode:
Code:
dependencies{
// Import the combined package of the bank card recognition plug-in and recognition capability.
implementation 'com.huawei.hms:ml-computer-card-bcr:2.0.3.301'
}
Mode 2: Base SDK Integration
The sample code is as follows:
Code:
dependencies{
// Import the bank card recognition plug-in package.
implementation 'com.huawei.hms:ml-computer-card-bcr-plugin:2.0.3.301'
}
Adding the Configuration to the File Header
After integrating the SDK in either mode, add the following information under apply plugin: 'com.android.application' in the file header:
Code:
apply plugin: 'com.huawei.agconnect'
Development Process
1. Create the recognition result callback function and reload the onSuccess, onCanceled, onFailure, and onDenied methods.
onSuccess indicates that the recognition is successful.
MLBcrCaptureResult indicates the recognition result.
onCanceled indicates that the user cancels the recognition.
onFailure indicates that the recognition fails.
onDenied indicates that the recognition request is denied, For example the camera is unavailable.
Code:
private void initCallBack() {
callback = new MLBcrCapture.Callback() {
@Override
public void onSuccess(MLBcrCaptureResult bankCardResult) {
if (bankCardResult != null) {
String cardNumber = bankCardResult.getNumber();
String cardExpire = bankCardResult.getExpire();
String cardIssuer = bankCardResult.getIssuer();
String cardType = bankCardResult.getType();
String cardOrganization = bankCardResult.getOrganization();
CardModel cardModel = new CardModel(cardNumber, cardExpire, cardIssuer, cardType, cardOrganization);
Intent intent = new Intent(ScanCardActivity.this, PaymentActivity.class);
intent.putExtra("CardData", cardModel);
startActivity(intent);
}
// Processing for successful recognition.
}
@Override
public void onCanceled() {
// Processing for recognition request cancelation.
}
// Callback method used when no text is recognized or a system exception occurs during recognition.
// retCode: result code.
// bitmap: bank card image that fails to be recognized.
@Override
public void onFailure(int retCode, Bitmap bitmap) {
// Processing logic for recognition failure.
}
@Override
public void onDenied() {
// Processing for recognition request deny scenarios, for example, the camera is unavailable.
}
};
}
2. Set the recognition parameters for calling the captureFrame API of the recognizer. The recognition result is returned through the callback function created in initCallBack method.
Code:
private void startCaptureActivity(MLBcrCapture.Callback callback) {
MLBcrCaptureConfig config = new MLBcrCaptureConfig.Factory()
// Set the expected result type of bank card recognition.
// MLBcrCaptureConfig.RESULT_NUM_ONLY: Recognize only the bank card number.
// MLBcrCaptureConfig.RESULT_SIMPLE: Recognize only the bank card number and validity period.
// MLBcrCaptureConfig.ALL_RESULT: Recognize information such as the bank card number, validity period, issuing bank, card organization, and card type.
.setResultType(MLBcrCaptureConfig.RESULT_ALL)
// Set the recognition screen display orientation.
// MLBcrCaptureConfig.ORIENTATION_AUTO: adaptive mode. The display orientation is determined by the physical sensor.
// MLBcrCaptureConfig.ORIENTATION_LANDSCAPE: landscape mode.
// MLBcrCaptureConfig.ORIENTATION_PORTRAIT: portrait mode.
.setOrientation(MLBcrCaptureConfig.ORIENTATION_AUTO)
.create();
MLBcrCapture bankCapture = MLBcrCaptureFactory.getInstance().getBcrCapture(config);
bankCapture.captureFrame(this, callback);
}
3. In the callback of the recognition button, call the method defined in startCaptureActivity method to implement bank card recognition
Code:
@Override
public void onClick(View view) {
switch (view.getId()) {
// Detection button.
case R.id.btn_scan_bank_card:
if (checkPermissions())
startCaptureActivity(callback);
break;
default:
break;
}
}
Adding Permissions
CAMERA: To use the camera on the device for recognition or detection, your app needs to apply for the camera permission.
READ_EXTERNAL_STORAGE: To use general card recognition plug-in, your app needs to apply for the file read permission.
The procedure is as follows:
1. Specify permissions in the AndroidManifest.xml file.
Code:
<!--Camera permission-->
<uses-permission android:name="android.permission.CAMERA" />
<!--Read permission-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2. After specifying the permissions in the AndroidManifest.xml file, dynamically apply for the permissions in the code for Android 6.0 and later versions.
Code:
private int PERMISSION_REQUEST_CODE = 10;
private boolean checkPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// The app has the camera permission.
return true;
} else {
// Apply for the camera permission.
requestCameraPermission();
}
return false;
}
private void requestCameraPermission() {
final String[] permissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0) {
boolean cameraAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean storageAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (cameraAccepted && storageAccepted ) {
Toast.makeText(ScanCardActivity.this, "Permission Granted, Now you can access camera", Toast.LENGTH_SHORT).show();
startCaptureActivity(callback);
} else {
requestCameraPermission();
}
}
}
}
Find the activity_scan_card.xml as follows
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_scan_bank_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="50dp"
android:background="@drawable/btn_background"
android:text="Scan Bank Card"
android:textAllCaps="false"
android:textColor="#fff" />
</RelativeLayout>
In the onSuccess method we used one CardModel class implemented with Parcelable interface. Here CardModel is used for passing data through intent to PaymentActivity. Check the CardModel class below
Code:
public class CardModel implements Parcelable {
private String cardNumber;
private String cardExpire;
private String cardIssuer;
private String cardType;
private String cardOrganization;
public CardModel(String cardNumber, String cardExpire, String cardIssuer, String cardType,String cardOrganization) {
this.cardNumber = cardNumber;
this.cardExpire = cardExpire;
this.cardIssuer = cardIssuer;
this.cardType = cardType;
this.cardOrganization = cardOrganization;
}
protected CardModel(Parcel in) {
cardNumber = in.readString();
cardExpire = in.readString();
cardIssuer = in.readString();
cardType = in.readString();
cardOrganization = in.readString();
}
public static final Creator<CardModel> CREATOR = new Creator<CardModel>() {
@Override
public CardModel createFromParcel(Parcel in) {
return new CardModel(in);
}
@Override
public CardModel[] newArray(int size) {
return new CardModel[size];
}
};
public String getCardNumber() {
return cardNumber;
}
public String getCardExpire() {
return cardExpire;
}
public String getCardIssuer() {
return cardIssuer;
}
public String getCardType() {
return cardType;
}
public String getCardOrganization() {
return cardOrganization;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(cardNumber);
parcel.writeString(cardExpire);
parcel.writeString(cardIssuer);
parcel.writeString(cardType);
parcel.writeString(cardOrganization);
}
}
After reconizing card details we are passing all the details to PaymentActivity for further steps. Check the activity_payment.xml below
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000">
<RelativeLayout
android:id="@+id/rl_tool"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Payment"
android:textColor="#fff"
android:textSize="16sp"
android:textStyle="bold" />
</RelativeLayout>
<androidx.cardview.widget.CardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_below="@+id/rl_tool"
android:layout_margin="10dp"
app:cardCornerRadius="10dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/chip" />
<TextView
android:id="@+id/txt_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="YOUR NAME"
android:textColor="#fff"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_card_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/txt_valid"
android:layout_centerHorizontal="true"
android:shadowColor="#7F000000"
android:shadowDx="1"
android:shadowDy="2"
android:shadowRadius="5"
android:textColor="#FBFBFB"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_valid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/img_organization"
android:layout_centerHorizontal="true"
android:text="VALID\nTHRU"
android:textColor="#fff"
android:textSize="10sp" />
<TextView
android:id="@+id/txt_expiry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/txt_card_number"
android:layout_centerHorizontal="true"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@id/txt_valid"
android:textColor="#fff"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_card_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:textAllCaps="true"
android:textColor="#fff"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/img_organization"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/card"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Verify your Card information"
android:textColor="#fff"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Card Number"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_valid_thru"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Valid Thru"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColorHint="#fff"
app:hintTextColor="#fff">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edt_cvv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="CVV"
android:inputType="textPassword"
android:text="***"
android:textColor="#fff" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:background="@drawable/btn_background"
android:paddingStart="20dp"
android:paddingTop="5dp"
android:paddingEnd="20dp"
android:paddingBottom="5dp"
android:text="Pay Now"
android:textColor="#fff"
android:textSize="12sp" />
</RelativeLayout>
In the PaymentActivity, We differentiated cards based on getCardOrganization method. Added different background colors and related data. Check the PaymentActivity code below
Code:
public class PaymentActivity extends AppCompatActivity {
private TextView txtCardNumber;
private TextView txtCardType;
private TextView txtExpire;
private TextView txtUserName;
private ImageView imgOrganization;
private CardView cardBackground;
private TextInputEditText edtCardNumber;
private TextInputEditText edtValid;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
init();
Intent intent = getIntent();
if (intent != null) {
CardModel cardModel = intent.getParcelableExtra("CardData");
if (cardModel != null) {
setCardData(cardModel);
}
}
}
private void setCardData(CardModel cardModel) {
String cardNumber = cardModel.getCardNumber().replaceAll("....", "$0 ");
txtCardNumber.setText(cardNumber);
edtCardNumber.setText(cardNumber);
txtExpire.setText(cardModel.getCardExpire());
edtValid.setText(cardModel.getCardExpire());
if (cardModel.getCardType() == null) {
txtCardType.setText("CARD TYPE");
} else {
txtCardType.setText(cardModel.getCardType());
}
String cardOrganization = cardModel.getCardOrganization();
if (cardOrganization != null) {
if (cardOrganization.equalsIgnoreCase("MASTERCARD")) {
setCardBackgroundAndOrganization(R.drawable.master_card, "#c22e67");
} else if (cardOrganization.equalsIgnoreCase("VISA")) {
setCardBackgroundAndOrganization(R.drawable.visa, "#4812e8");
} else if (cardOrganization.equalsIgnoreCase("UnionPay")) {
imgOrganization.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.union));
cardBackground.setCardBackgroundColor(Color.parseColor("#918B8B"));
Shader shader = new LinearGradient(70, 50, 100, 100, Color.RED, Color.BLACK, Shader.TileMode.CLAMP);
txtCardType.getPaint().setShader(shader);
}
} else {
txtCardNumber.setTextColor(Color.BLACK);
txtExpire.setTextColor(Color.BLACK);
txtCardType.setTextColor(Color.BLACK);
txtUserName.setTextColor(Color.BLACK);
}
Toast.makeText(this, cardModel.getCardOrganization() + " " + cardModel.getCardIssuer() + " " + cardModel.getCardType(), Toast.LENGTH_LONG).show();
}
private void init() {
txtCardNumber = findViewById(R.id.txt_card_number);
txtCardType = findViewById(R.id.txt_card_type);
txtExpire = findViewById(R.id.txt_expiry);
imgOrganization = findViewById(R.id.img_organization);
cardBackground = findViewById(R.id.card);
edtCardNumber = findViewById(R.id.edt_card_number);
edtValid = findViewById(R.id.edt_valid_thru);
txtUserName = findViewById(R.id.txt_user_name);
}
private void setCardBackgroundAndOrganization(int cardOrganization, String backgroundColor) {
imgOrganization.setImageDrawable(ContextCompat.getDrawable(this, cardOrganization));
cardBackground.setCardBackgroundColor(Color.parseColor(backgroundColor));
Shader shader = new LinearGradient(0, 0, 0, 100, Color.WHITE, Color.DKGRAY, Shader.TileMode.CLAMP);
txtCardType.getPaint().setShader(shader);
}
}
Find the output in below images
Tips and Tricks
We need to use CAMERA and READ_EXTERNAL_STORAGE permissions. It is recognizing cardNumber, cardType, cardIssuer and cardOrganization but it is not recognizing cardHolderName. For some of the cards it is returning same value and some cards null values for cardType, cardIssuer and cardOrganization
Conclusion
In this article we can learn about how to scan and get bank card details by using ML Kit Bank card recognition and also populating the recognized data to payment screen with good User Interface.
Reference links
https://developer.huawei.com/consum...Guides/bank-card-recognition-0000001050038118

Let's develop Music Station Android app for Huawei Vision S (Smart TV)

{
"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"
}
Article Introduction
In this article, we will develop Music Station android app for Huawei Vision S (Smart TV) devices. Huawei Vision S is a brand new large screen category and important part of Huawei's "1+8+N" full-scenario services and Huawei Developer Ecosystem. Since, Huawei Vision S system architecture supports AOSP project framework, we used Leanback Library which offers extensive features for large screens to develop our user experience.
Why an app for Huawei Vision S?
Large screen offers better visibility and enhanced user experience. Due to Covid-19 lockdown, Smart TV has grown to include over 80% more users than it had this time last year. Total distribution of usage for TV is increasing rapidly. As a result of this, total number of TV apps has jumped dramatically including educational and entertainment apps.
Designing App for Huawei Vision S
While designing an app for Huawei Vision S, we have to keep following key points in our mind:
Build Layout for TV: We must design landscape orientation layout that allows users to easily see the screen 10 feet away from the TV.
Management Controller: Our app must support arrow keys and handle offline controllers as well as inputs from multiple controllers.
For this article, we implemented Leanback Library which offers amazing and interactive user experience for apps such as Audio/Video players and so on.
Pre-Requisites
Before getting started, following are the requirements:
Android Studio (During this tutorial, we used version 4.1.1)
Android SDK 24 or later
Huawei Vision S for testing
Development
Following are the major steps of development for this article:
Step 1: Add Dependencies & Permissions
1.1: Add the following dependencies in the app level build.gradle file:
Java:
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// TV Libs
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.leanback:leanback-preference:1.0.0'
// General
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.nabinbhandari.android:permissions:3.8'
// Animation
implementation 'com.airbnb.android:lottie:3.7.0'
implementation 'com.gauravk.audiovisualizer:audiovisualizer:0.9.2'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation ('com.github.bumptech.glide:okhttp3-integration:4.12.0'){
exclude group: 'glide-parent'
}
}
1.2: We are developing this app only for TV. So, we will disable the Touch_Screen requirements and enable the Leanback. For the audio visualizer, we need Record_Audio permission. Add the following permissions and features tag in the AndroidManifest.xml:
Java:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="true" />
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
1.3: Huawei Vision S supports Leanback Library but does not supports Leanback Launcher. So, we added the following tag in the SplashActivity inside the AndroidManifest.xml:
Code:
<activity android:name=".activities.SplashActivity"
android:screenOrientation="landscape"
android:banner="@drawable/app_icon"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:launchMode="singleTop"
android:logo="@drawable/app_icon"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- HUAWEI Vision S only support CATEGORY_LAUNCHER -->
<category android:name="android.intent.category.LAUNCHER" />
<!-- ADD the below line only if you want to release the same code on Google Play -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
1.4: The banner icon is required when we are developing apps for TV. The size for Huawei Vision S banner icon is 496x280 which must be added in the drawable folder.
Step 2: Generating Supported Language JSON
Since our main goal is playing Music, we restricted data source and generated a JSON file locally to avoid API creation and API calling. In real world scenario, an API can be developed or can be used to get the real-time data.
Step 3: Building Layout
3.1: The most important layout of our application is of PlayerActivity. Add the following activity_player.xml layout file in the layout folder of the res. We developed this layout to enhance user experience and add custom views like Audio Visualizer. The layout has two main sub-layouts, Player Content View and Error View.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background"
android:keepScreenOn="true">
<ImageView
android:id="@+id/imgBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="false"
app:lottie_progress="53"
app:lottie_rawRes="@raw/splash_animation" />
<com.gauravk.audiovisualizer.visualizer.CircleLineVisualizer
android:id="@+id/blastVisualizer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:avColor="@color/light_red"
custom:avDensity="0.8"
custom:avSpeed="normal"
custom:avType="fill" />
<ImageView
android:id="@+id/imgOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:background="@android:color/black" />
<ProgressBar
android:id="@+id/progressBarLoader"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimaryDark"
android:indeterminateTintMode="src_atop"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/rlContentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:paddingStart="50dp"
android:paddingEnd="50dp">
<ImageView
android:id="@+id/imgIcon"
android:layout_width="80dp"
android:layout_height="80dp" />
<TextView
android:id="@+id/txtSongName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/imgIcon"
android:layout_marginTop="5dp"
android:textSize="@dimen/title_text_size" />
<TextView
android:id="@+id/txtArtistName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtSongName"
android:layout_marginTop="5dp"
android:textSize="@dimen/artist_text_size" />
<TextView
android:id="@+id/txtCategoryName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtArtistName"
android:layout_marginTop="5dp"
android:textSize="@dimen/category_text_size" />
<ImageButton
android:id="@+id/btnPlayPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtCategoryName"
android:layout_marginTop="50dp"
android:background="@drawable/ui_selector_bg"
android:focusable="true"
android:src="@drawable/lb_ic_play" />
<SeekBar
android:id="@+id/seekBarAudio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/btnPlayPause"
android:layout_marginTop="10dp"
android:background="@drawable/ui_selector_bg"
android:colorControlActivated="@color/white"
android:focusable="true"
android:progressTint="@color/white"
android:thumbTint="@color/white" />
<TextView
android:id="@+id/txtTotalDuration"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_alignEnd="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:text=" / -"
android:textSize="@dimen/time_text_size" />
<TextView
android:id="@+id/txtCurrentTime"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:layout_toStartOf="@+id/txtTotalDuration"
android:text="-"
android:textSize="@dimen/time_text_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rlErrorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:visibility="gone">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<ImageView
android:id="@+id/imgErrorLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/lb_ic_sad_cloud" />
<TextView
android:id="@+id/txtError"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imgErrorLoading"
android:layout_centerHorizontal="true"
android:text="@string/error_message"
android:textSize="@dimen/time_text_size" />
<Button
android:id="@+id/btnDismiss"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtError"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:focusable="true"
android:text="@string/dismiss_error" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
3.2: In this article, we used Lottie animation in the SplashActivity for better user experience inside the following activity_splash.xml.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/colorPrimaryDark"
tools:context=".activities.SplashActivity">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="true"
app:lottie_repeatCount="1"
app:lottie_rawRes="@raw/splash_animation" />
<ImageView
android:id="@+id/imgAppIcon"
android:src="@drawable/img_music_station_logo"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="40dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Step 4: Adding JAVA Classes
4.1: We extended the MainFragment class from BrowseFragment which is Leanback home layout component. This view handles the landing screen containing interactive side menu, main content area with different cards and navigation between them.
Java:
public class MainFragment extends BrowseFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setupEventListeners();
}
private void loadRows() {
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
CardPresenter cardPresenter = new CardPresenter();
int i;
for (i = 0; i < DataUtil.getData(getActivity()).size(); i++) {
CategoryModel categoryModel = DataUtil.getData(getActivity()).get(i);
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
for (int j = 0; j < categoryModel.getCategorySongs().size(); j++) {
listRowAdapter.add(categoryModel.getCategorySongs().get(j));
}
HeaderItem header = new HeaderItem(i, categoryModel.getCategoryName());
rowsAdapter.add(new ListRow(header, listRowAdapter));
}
setAdapter(rowsAdapter);
}
private void prepareBackgroundManager() {
BackgroundManager mBackgroundManager = BackgroundManager.getInstance(getActivity());
mBackgroundManager.attach(getActivity().getWindow());
mBackgroundManager.setColor(getResources().getColor(R.color.main_background));
DisplayMetrics mMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
private void setupUIElements() {
setTitle(getString(R.string.app_name));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(true);
setBrandColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
setSearchAffordanceColor(ContextCompat.getColor(getActivity(), R.color.colorPrimary));
}
private void setupEventListeners() {
setOnItemViewClickedListener(new ItemViewClickedListener());
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof Song) {
Song song = (Song) item;
Intent intent = new Intent(getActivity(), PlayerActivity.class);
intent.putExtra(DataUtil.SONG_DETAIL, song);
getActivity().startActivity(intent);
}
}
}
}
4.2: Cards are displayed using CardPresenter which is extended by Presenter from the Leanback library.
Java:
public class CardPresenter extends Presenter {
private static final int CARD_WIDTH = 313;
private static final int CARD_HEIGHT = 176;
private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
view.setBackgroundColor(color);
view.findViewById(R.id.info_field).setBackgroundColor(color);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
sDefaultBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.background);
sSelectedBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.colorPrimaryDark);
ImageCardView cardView =
new ImageCardView(parent.getContext()) {
@Override
public void setSelected(boolean selected) {
updateCardBackgroundColor(this, selected);
super.setSelected(selected);
}
};
cardView.setFocusable(true);
cardView.setFocusableInTouchMode(true);
updateCardBackgroundColor(cardView, false);
return new ViewHolder(cardView);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
Song song = (Song) item;
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setTitleText(song.getSongName());
cardView.setContentText(song.getArtistName());
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
Glide.with(viewHolder.view.getContext())
.load(song.getImageURL())
.centerCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(cardView.getMainImageView());
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setBadgeImage(null);
cardView.setMainImage(null);
}
}
4.3: Whenever user click on any Card Item, PlayerActivity is opened. Following are some of the important functions of the PlayerActivity.java. Please refer to the github link for complete code of this class.
Java:
private void handlePlayer(){
if (currentState == MediaPlayerHolder.PlayerState.PLAYING) {
pauseSong();
} else if (currentState == MediaPlayerHolder.PlayerState.PAUSED ||
currentState == MediaPlayerHolder.PlayerState.COMPLETED ||
currentState == MediaPlayerHolder.PlayerState.RESET) {
playSong();
}
}
private void setUI() {
if (song != null) {
txtSongName.setText(song.getSongName());
txtArtistName.setText(song.getArtistName());
txtCategoryName.setText(song.getCategoryName());
Glide.with(this)
.load(song.getImageURL())
.centerCrop()
.circleCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(imgIcon);
}
setupSeekBar();
}
private void pauseSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.PausePlayback());
}
private void playSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.StartPlayback());
}
private void resetSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.ResetPlayback());
}
public void log(StringBuffer formattedMessage) {
Log.d(PlayerActivity.class.getSimpleName(), String.format("log: %s", formattedMessage));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.UpdateLog event) {
log(event.formattedMessage);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackDuration event) {
seekBarAudio.setMax(event.duration);
setTotalDuration(event.duration);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackPosition event) {
if (!isUserSeeking) {
seekBarAudio.setProgress(event.position, true);
updateProgressTime(event.position);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.StateChanged event) {
hideLoader();
currentState = event.currentState;
switch (event.currentState) {
case PLAYING:
btnPlayPause.setImageResource(R.drawable.lb_ic_pause);
if (mMediaPlayerHolder.getAudioSessionId() != -1 && blastVisualizer != null){
blastVisualizer.setAudioSessionId(mMediaPlayerHolder.getAudioSessionId());
blastVisualizer.show();
}
break;
case PAUSED:
case RESET:
case COMPLETED:
btnPlayPause.setImageResource(R.drawable.lb_ic_play);
if (blastVisualizer != null){
blastVisualizer.hide();
}
break;
case ERROR:
showError();
break;
}
}
private void showError() {
if(song != null){
String title = song.getSongName();
String artist = song.getArtistName();
String songName = "Unable to play " + title + " (" + artist + ")";
txtError.setText(songName);
}
rlErrorLayout.setVisibility(View.VISIBLE);
btnDismiss.requestFocus();
}
private void setTotalDuration(int duration) {
long totalSecs = TimeUnit.MILLISECONDS.toSeconds(duration);
long hours = totalSecs / 3600;
long minutes = (totalSecs % 3600) / 60;
long seconds = totalSecs % 60;
String totalDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
totalDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
String text = " / " + totalDuration;
txtTotalDuration.setText(text);
}
private void updateProgressTime(int position){
long currentSecs = TimeUnit.MILLISECONDS.toSeconds(position);
long hours = currentSecs / 3600;
long minutes = (currentSecs % 3600) / 60;
long seconds = currentSecs % 60;
String currentDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
currentDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
txtCurrentTime.setText(currentDuration);
}
4.4: We used EventBus to asynchronously notify the PlayerActivity UI changes. The MediaPlayerHolder.java class handles the MediaPlayer states and manages the functionalities like playing, pause and seekbar position updates. Please refer to the github link for complete code of this class.
Java:
public int getAudioSessionId(){
if(mMediaPlayer != null){
return mMediaPlayer.getAudioSessionId();
} else
return -1;
}
public void release() {
logToUI("release() and mMediaPlayer = null");
mMediaPlayer.release();
EventBus.getDefault().unregister(this);
}
public void stop() {
logToUI("stop() and mMediaPlayer = null");
mMediaPlayer.stop();
}
public void play() {
if (!mMediaPlayer.isPlaying()) {
logToUI(String.format("start() %s", urlPath));
mMediaPlayer.start();
startUpdatingSeekbarWithPlaybackProgress();
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
}
}
public void pause() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
logToUI("pause()");
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PAUSED));
}
}
public void reset() {
logToUI("reset()");
mMediaPlayer.reset();
load(urlPath);
stopUpdatingSeekbarWithPlaybackProgress(true);
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.RESET));
}
public void load(String url) {
this.urlPath = url.replaceAll(" ", "%20");
if (mMediaPlayer != null) {
try {
mMediaPlayer.setAudioAttributes(
new AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer player) {
initSeekbar();
play();
}
});
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
return false;
}
});
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer arg0) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
SystemClock.sleep(200);
mMediaPlayer.start();
}
});
logToUI("load() {1. setDataSource}");
mMediaPlayer.setDataSource(urlPath);
logToUI("load() {2. prepare}");
mMediaPlayer.prepareAsync();
} catch (Exception e) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
logToUI(e.toString());
}
}
}
public void seekTo(int duration) {
logToUI(String.format(Locale.ENGLISH, "seekTo() %d ms", duration));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mMediaPlayer.seekTo(duration, MediaPlayer.SEEK_CLOSEST);
else
mMediaPlayer.seekTo(duration);
}
private void stopUpdatingSeekbarWithPlaybackProgress(boolean resetUIPlaybackPosition) {
if (mExecutor != null) {
mExecutor.shutdownNow();
}
mExecutor = null;
mSeekbarProgressUpdateTask = null;
if (resetUIPlaybackPosition) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackPosition(0));
}
}
private void startUpdatingSeekbarWithPlaybackProgress() {
// Setup a recurring task to sync the mMediaPlayer position with the Seekbar.
if (mExecutor == null) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
}
if (mSeekbarProgressUpdateTask == null) {
mSeekbarProgressUpdateTask = new Runnable() {
@Override
public void run() {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
int currentPosition = mMediaPlayer.getCurrentPosition();
EventBus.getDefault().post(
new LocalEventFromMediaPlayerHolder.PlaybackPosition(
currentPosition));
}
}
};
}
mExecutor.scheduleAtFixedRate(
mSeekbarProgressUpdateTask,
0,
SEEKBAR_REFRESH_INTERVAL_MS,
TimeUnit.MILLISECONDS
);
}
public void initSeekbar() {
// Set the duration.
final int duration = mMediaPlayer.getDuration();
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackDuration(duration));
logToUI(String.format(Locale.ENGLISH, "setting seekbar max %d sec", TimeUnit.MILLISECONDS.toSeconds(duration)));
}
When user click Select button on the Remote Control of Huawei Vision S on any item, the player view is opened and MediaPlayer start loading the song url. Once the song is loaded, it starts playing and the icon of Play changes to Pause. The Audio Visualizer takes AudioSessionId to sync with audio song. By default, seekbar is selected for the user to skip the songs using Right and Left buttons of the Remote Control. The duration of the song updates based on seekbar position.
Step 5: Run the application
We have added all the required code. Now, just build the project, run the application and test on Huawei Vision S.
Conclusion
Using Leanback, developers can develop beautifully crafted android applications with amazing UI/UX experience for Huawei Vision S. They can also enhance their user engagement and behavior. Combining different Huawei Kits supported by Vision S like Account or IAP can yield amazing results.
Tips and Tricks
You must use default Launcher if you are developing app for Huawei Vision S.
Leanback Launcher is not supported by Huawei Vision S.
If you have same code base for Mobile and Vision S devices, you can use TVUtils class to check at run-time about the device and offer functionalities based on it.
Make sure to add all the permissions like RECORD_AUDIO, INTERNET.
Make sure to add run-time permissions check. In this article, we used 3rd party Permission Check library with custom Dialog if user deny any of the required permission.
Always use animation libraries like Lottie or ViewAnimator to enhance UI/UX in your application.
We used AudioVisualizer library to bring Music feel on our Player UI.
References
Android TV Documentation:
https://developer.android.com/training/tv/start
https://developer.android.com/training/tv/start/layouts
https://developer.android.com/training/tv/start/controllers
https://developer.android.com/training/tv/start/navigation
Lottie Android Documentation:
http://airbnb.io/lottie/#/android
Github Code Link:
https://github.com/yasirtahir/MusicStationTV
Original Source

Webview Issue

Video calling feature was working perfectly with webview until almost 3 months ago. Recently it's not supporting camera and audio functionality, even browser and application have both of these permissions. Would like to know is it because of any recent update with webview or chromium or with anything else, as it was working properly before.
Below is the method used to load the url :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getBindingModel().webView.settings.javaScriptEnabled = true
getBindingModel().webView.settings.mediaPlaybackRequiresUserGesture = false
getBindingModel().webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return false
}
}
getBindingModel().webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
getBindingModel().loadingText.text = "$newProgress%"
getBindingModel().loadingText.visibility =
if (newProgress > 99) View.GONE else View.VISIBLE
getBindingModel().progress.visibility =
if (newProgress > 99) View.GONE else View.VISIBLE
}
override fun onPermissionRequest(request: PermissionRequest?) {
runOnUiThread { request?.grant(request.resources) }
}
}
}
Manifest permissions:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<uses-permission android:name="android.webkit.resource.AUDIO_CAPTURE" />
<uses-permission android:name="android.webkit.resource.VIDEO_CAPTURE" />
Response regarding this issue is highly appreciated and thanks in advance. Attaching the screenshot for more information.

someone can tell me why my app can't run in anroid13?

the app can run in huawei nova5zd, OS is HarmonyOS 2.0.0, but can't run in pixel4xl, android 13, showing "no permission", the main code is
Java:
public class TestLocationActivity extends AppCompatActivity {
public static final int LOCATION_CODE = 301;
public static final String TAG = "TestLocationActivity:wp";
private LocationManager locationManager;
private String locationProvider = null;
com.adan.gpsdemo.databinding.ActivityTestBinding activityTestBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityTestBinding = ActivityTestBinding.inflate(getLayoutInflater());
setContentView(activityTestBinding.getRoot());
getLocation();
}
private void getLocation(){
//1.获取位置管理器
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//2.获取位置提供器,GPS或是NetWork
List<String> providers = locationManager.getProviders(true);
if (providers.contains(LocationManager.GPS_PROVIDER)) {
//如果是GPS
locationProvider = LocationManager.GPS_PROVIDER;
Log.v(TAG, "定位方式GPS");
} else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
//如果是Network
locationProvider = LocationManager.NETWORK_PROVIDER;
Log.v(TAG, "定位方式Network");
}else {
Toast.makeText(this, "没有可用的位置提供器", Toast.LENGTH_SHORT).show();
return;
}
if (Build.VERSION_CODES.M <= Build.VERSION.SDK_INT) {
//获取权限(如果没有开启权限,会弹出对话框,询问是否开启权限)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
//请求权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE);
} else {
//3.获取上次的位置,一般第一次运行,此值为null
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location!=null){
// notice textview change
showGPSValue(location.getLongitude(),location.getLatitude());
Log.v(TAG, "获取上次的位置-经纬度:"+location.getLongitude()+" "+location.getLatitude());
getAddress(location);
}else{
//监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace
locationManager.requestLocationUpdates(locationProvider, 3000, 1,locationListener);
}
}
} else {
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location!=null){
showGPSValue(location.getLongitude(),location.getLatitude());
Log.v(TAG, "获取上次的位置-经纬度:"+location.getLongitude()+" "+location.getLatitude());
getAddress(location);
}else{
//监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace
locationManager.requestLocationUpdates(locationProvider, 3000, 1,locationListener);
}
}
}
@SuppressLint("SetTextI18n")
private void showGPSValue(double longitude, double latitude){
new Thread(()->{
// 39.951111, 75.172778 change to 75°10'22''E,39°57'04''N
int hour = (int) longitude;
double minute = getDecimalValue(longitude) * 60;
double second = getDecimalValue(minute) * 60;
int hour_lat = (int) latitude;
double minute_lat = getDecimalValue(latitude) * 60;
double second_lat = getDecimalValue(minute_lat) * 60;
String strLong = hour + "°" + (int)minute + "'" + (int)second + "\"";
String strLat = hour_lat + "°" + (int)minute_lat + "'" + (int)second_lat + "\"";
Log.i(TAG,"longitude:" + strLong);
Log.i(TAG,"latitude:" + strLat);
activityTestBinding.tvGpsValue.setText(strLong + "E," + strLat + "N");
}).start();
}
/**
* get decimal of a value
* @param value double include int and decimal
* @return decimal of value
*/
private double getDecimalValue(double value){
long longPart = (long) value;
BigDecimal bigDecimal = new BigDecimal(Double.toString(value));
BigDecimal bigDecimalLongPart = new BigDecimal(Double.toString(longPart));
double dPoint = bigDecimal.subtract(bigDecimalLongPart).doubleValue();
Log.i(TAG,"DecimalValue:" + dPoint);
return dPoint;
}
public LocationListener locationListener;
{
locationListener = new LocationListener() {
// Provider的状态在可用、暂时不可用和无服务三个状态直接切换时触发此函数
@SuppressWarnings("deprecation")
@Contract(pure = true)
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
// Provider被enable时触发此函数,比如GPS被打开
@Override
public void onProviderEnabled(String provider) {
}
// Provider被disable时触发此函数,比如GPS被关闭
@Override
public void onProviderDisabled(String provider) {
}
//当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发
@Override
public void onLocationChanged(Location location) {
if (location != null) {
//如果位置发生变化,重新显示地理位置经纬度
showGPSValue(location.getLongitude(),location.getLatitude());
Log.v(TAG, "监视地理位置变化-经纬度:" + location.getLongitude() + " " + location.getLatitude());
}
}
};
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "申请权限", Toast.LENGTH_LONG).show();
try {
List<String> providers = locationManager.getProviders(true);
if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
//如果是Network
locationProvider = LocationManager.NETWORK_PROVIDER;
} else if (providers.contains(LocationManager.GPS_PROVIDER)) {
//如果是GPS
locationProvider = LocationManager.GPS_PROVIDER;
}
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
showGPSValue(location.getLongitude(),location.getLatitude());
Log.v("TAG", "获取上次的位置-经纬度:" + location.getLongitude() + " " + location.getLatitude());
} else {
// 监视地理位置变化,第二个和第三个参数分别为更新的最短时间minTime和最短距离minDistace
locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener);
}
} catch (SecurityException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "缺少权限", Toast.LENGTH_LONG).show();
finish();
}
} else {
throw new IllegalStateException("Unexpected value: " + requestCode);
}
}
//获取地址信息:城市、街道等信息
private void getAddress(Location location) {
List<Address> result;
try {
if (location != null) {
Geocoder gc = new Geocoder(this, Locale.getDefault());
result = gc.getFromLocation(location.getLatitude(),
location.getLongitude(), 1);
new Thread(()->{
String addValue = result.toString();
activityTestBinding.tvAddressValue.setText(addValue);
}).start();
Log.v(TAG, "获取地址信息:"+ result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
locationManager.removeUpdates(locationListener);
}
}
Click to expand...
Click to collapse
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="ExtraText">
<!-- 粗略的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 精确的位置权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GPSDemo">
<activity
android:name=".TestFusedLocationProviderClientActivity"
android:exported="true">
<!-- <meta-data-->
<!-- android:name="android.app.lib_name"-->
<!-- android:value="" />-->
</activity>
<activity android:name=".MainActivity"></activity>
<activity android:name=".TestGPS2"></activity>
<activity android:name=".TestLocationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Test1" />
<activity android:name=".Test2" />
</application>
</manifest>
whloe code is
GitHub - yhm2046/GPSDemo: test gps
test gps. Contribute to yhm2046/GPSDemo development by creating an account on GitHub.
github.com
mm11751 said:
the app can run in huawei nova5zd, OS is HarmonyOS 2.0.0, but can't run in pixel4xl, android 13, showing "no permission", the main code is
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="ExtraText">
<!-- 粗略的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 精确的位置权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GPSDemo">
<activity
android:name=".TestFusedLocationProviderClientActivity"
android:exported="true">
<!-- <meta-data-->
<!-- android:name="android.app.lib_name"-->
<!-- android:value="" />-->
</activity>
<activity android:name=".MainActivity"></activity>
<activity android:name=".TestGPS2"></activity>
<activity android:name=".TestLocationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Test1" />
<activity android:name=".Test2" />
</application>
</manifest>
whloe code is
GitHub - yhm2046/GPSDemo: test gps
test gps. Contribute to yhm2046/GPSDemo development by creating an account on GitHub.
github.com
Click to expand...
Click to collapse
Most likely because Android 13 includes behavior changes that may affect your app. The following behavior changes apply exclusively to apps that are targeting Android 13 or higher.
Privacy
Granular media permissions
Use of body sensors in the background requires new permission
Performance and battery
User experience
App color theme applied automatically to WebView content
Google Play services
Updated non-SDK restrictions
rodken said:
Most likely because Android 13 includes behavior changes that may affect your app. The following behavior changes apply exclusively to apps that are targeting Android 13 or higher.
Privacy
Granular media permissions
Use of body sensors in the background requires new permission
Performance and battery
User experience
App color theme applied automatically to WebView content
Google Play services
Updated non-SDK restrictions
Click to expand...
Click to collapse
I modify some code , but sitll don't work. Now how can I do ?
{
"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"
}
mm11751 said:
I modify some code , but sitll don't work. Now how can I do ?
Click to expand...
Click to collapse
Try API Level 33.
rodken said:
Try API Level 33.
Click to expand...
Click to collapse
I modified this code, still no work
mm11751 said:
I modified this code, still no work
Click to expand...
Click to collapse
Are you the developer of the app?
rodken said:
Are you the developer of the app?
Click to expand...
Click to collapse
yes, I wrote it
mm11751 said:
yes, I wrote it
Click to expand...
Click to collapse
You might want to redirect your question(s) to this forum to receive more than one insight on the issue.

Categories

Resources