Commit 46da4562 authored by bixing's avatar bixing

Initial commit

parents
*.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
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.application'
}
android {
namespace 'com.ads.cal.calculator'
compileSdk 34
defaultConfig {
applicationId "com.ads.cal.calculator"
minSdk 23
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
debuggable false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'app-proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
signingConfigs {
toolsTest {
storeFile file("../toolsTest.jks")
storePassword '123456'
keyAlias 'key0'
keyPassword '123456'
}
}
flavorDimensions "versionName"
productFlavors {
tools {
applicationId "com.ads.cal.calculator"
versionName "1.0.0.0"
signingConfig signingConfigs.toolsTest
manifestPlaceholders = [google_ad_app_id: "ca-app-pub-3940256099942544~3347511713"]
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.ezylang:EvalEx:3.0.5'
implementation 'com.google.android.gms:play-services-ads:22.4.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime:2.6.2"
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
// Import the BoM for the Firebase platform
implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
// Add the dependency for the Analytics library
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation("com.google.firebase:firebase-analytics")
}
\ No newline at end of file
# 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
package com.ads.cal.calculator;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.ads.cal.calculator", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0.0">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="${google_ad_app_id}" />
<activity
android:name=".activity.SplashActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.CalculatorActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.CompassActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.DecibelActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.SettingActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.AboutActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
<activity
android:name=".activity.WebActivity"
android:configChanges="keyboard|keyboardHidden|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Calculator" />
</application>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.sensor.accelerometer" />
<uses-feature android:name="android.hardware.sensor.compass" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>Privacy Policy</title>
</head>
<body>
<h1>Privacy Policy</h1>
<p>Effective Date: September 20, 2023</p>
<h2>1. Information Collection and Use</h2>
<p>Our application (hereinafter referred to as the "App") is designed to provide tools such as a calculator, compass, and sound level meter. In the process of providing these features, we may collect and use the following information:</p>
<h3>1.1 Location Information</h3>
<p>The App uses your device's location permission to provide relevant features, such as the compass. We do not associate location information with personal identifiers and only access this information when you use related features.</p>
<h3>1.2 Recording Information</h3>
<p>The App requires recording permission to provide the sound level meter feature. Recording information is used solely for calculating sound levels and is not stored, shared, or used for other purposes.</p>
<h3>1.3 Sensor Information</h3>
<p>The App uses the accelerometer and compass sensors to provide features such as the compass. Sensor information is used solely for the normal operation of the App and is not stored or shared.</p>
<h2>2. Analytics</h2>
<p>We use Firebase Analytics to collect anonymous usage data of the App to improve performance and user experience. Firebase Analytics may collect information about your device and App usage but does not include personal identifiers. For more information, please visit <a href="https://firebase.google.com/policies/analytics">Firebase Privacy Policy</a>.</p>
<h2>3. Children's Privacy</h2>
<p>Our App does not knowingly collect personal information from children under the age of 13. If you believe that we have unintentionally collected such information, please contact us immediately so that we can take appropriate action to delete the information.</p>
<h2>4. Security</h2>
<p>We take reasonable measures to protect your information from unauthorized access, use, or disclosure. However, please be aware that internet transmissions are never completely secure, and we cannot guarantee the absolute security of your information.</p>
<h2>5. Changes to Privacy Policy</h2>
<p>We reserve the right to update this privacy policy at any time. The updated privacy policy will take effect within the App and will be posted on this page. Please check this page regularly for any changes.</p>
</body>
</html>
\ No newline at end of file
package com.ads.cal.calculator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.ads.cal.calculator.activity.SplashActivity;
import com.ads.cal.calculator.ad.AppOpenAdManager;
import com.ads.cal.calculator.utils.EventIdUtils;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
import com.ads.cal.calculator.utils.LogUtils;
import com.google.android.gms.ads.MobileAds;
public class MyApplication extends Application
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
@SuppressLint("StaticFieldLeak")
private static MyApplication application;
private Activity currentActivity;
// private AppOpenAdManager appOpenAdManager;
public static MyApplication getApplication() {
return application;
}
// public AppOpenAdManager getAppOpenAdManager() {
// return appOpenAdManager;
// }
private int foregroundPageNumbers = 0;
public boolean isHotStartUp() {
boolean isHotStat = foregroundPageNumbers == 1;
LogUtils.log("isHotStat = " + isHotStat);
return isHotStat;
}
public static long startTime = 0;
@Override
public void onCreate() {
startTime = System.currentTimeMillis();
LogUtils.log("application", "init start Time == "+ startTime);
super.onCreate();
application = this;
this.registerActivityLifecycleCallbacks(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(application);
// initAd();
FireBaseAnalyticsUtils.init();
LogUtils.setConsoleLoggingEnabled(true);
LogUtils.log("application", "init finish Time == "+ (System.currentTimeMillis() - startTime) / 1000);
}
/**
* DefaultLifecycleObserver method that shows the app open ad when the app moves to foreground.
*/
@Override
public void onStart(@NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onStart(owner);
LogUtils.log("currentActivity == " + currentActivity);
// Show the ad (if available) when the app moves to foreground.
if (currentActivity instanceof SplashActivity) {
return;
}
// if (appOpenAdManager.isAdAvailable()) {
// FireBaseAnalyticsUtils.init().send(EventIdUtils.AD_OPEN_HAS_CACHE);
// } else {
// FireBaseAnalyticsUtils.init().send(EventIdUtils.AD_OPEN_NOT_CACHE);
// }
// appOpenAdManager.showAdIfAvailable(currentActivity);
}
private void initAd() {
// MobileAds.initialize(application, initializationStatus -> {
// });
// initAppOpenAdManager();
// appOpenAdManager.loadAd();
}
private void initAppOpenAdManager() {
// appOpenAdManager = new AppOpenAdManager();
}
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
foregroundPageNumbers--;
// if (!appOpenAdManager.isShowingAd()) {
// currentActivity = activity;
// }
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
LogUtils.log("onActivityStarted --- " + activity.getLocalClassName());
boolean isHotStartUp = isHotStartUp();
foregroundPageNumbers++;
if (isHotStartUp) {
FireBaseAnalyticsUtils.init().send(EventIdUtils.HOT_START);
}
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
LogUtils.log("onActivityResumed --- " + activity.getLocalClassName());
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
LogUtils.log("onActivityPaused --- " + activity.getLocalClassName());
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
LogUtils.log("onActivityStopped --- " + activity.getLocalClassName());
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
LogUtils.log("onActivitySaveInstanceState --- " + activity.getLocalClassName());
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
LogUtils.log("onActivityDestroyed --- " + activity.getLocalClassName());
}
}
package com.ads.cal.calculator.activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.view.View;
import android.widget.TextView;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
public class AboutActivity extends BaseActivity {
@Override
protected String getPageExpEventId() {
return EventIdUtils.ABOUT_US_EXP;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_about;
}
@Override
protected void initView() {
TextView textView = findViewById(R.id.text);
try {
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);
String appVersion = packageInfo.versionName;
int appVersionCode = packageInfo.versionCode;
textView.setText(String.format("VersionName: %s VersionCode: %d", appVersion, appVersionCode));
} catch (Exception e) {
}
findViewById(R.id.back).setOnClickListener(view -> finish());
}
@Override
protected void initData() {
}
public static void invoke(Context context) {
Intent intent = new Intent(context, AboutActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.calculator.activity;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
import com.ads.cal.calculator.utils.LocationMgr;
import com.ads.cal.calculator.utils.SensorUtils;
import java.text.DecimalFormat;
public class CompassActivity extends BaseActivity {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 123; // 自定义的请求码
private LocationMgr locationMgr;
@Override
protected String getPageExpEventId() {
return EventIdUtils.COMPASS_EXP;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_compass;
}
private ImageView compass, compress_direction;
private TextView degrees;
private TextView lat, latTextView, lot, lotTextView;
private SensorEventListener sensorEventListener;
private SensorManager sensorManager;
private Sensor accelerometer, magnetometer;
private double lastDegrees;
private double currentDegrees;
private long lastUpdateTime = 0;
private static final float ALPHA = 0.9f; // 调整这个值以控制平滑程度
@Override
protected void initView() {
compass = findViewById(R.id.compress);
compress_direction = findViewById(R.id.compress_direction);
degrees = findViewById(R.id.degrees);
lat = findViewById(R.id.lat);
latTextView = findViewById(R.id.lat_text);
lot = findViewById(R.id.lot);
lotTextView = findViewById(R.id.lot_text);
}
@Override
protected void initData() {
initSensorManager();
// 初始化LocationMgr,传递一个LocationCallback
locationMgr = new LocationMgr((latitude, longitude) -> {
// 在此处处理获取到的位置数据
if (latitude.length > 1) {
lat.setText(latitude[1]);
}
if (latitude.length > 0) {
latTextView.setText(latitude[0]);
}
if (longitude.length > 1) {
lot.setText(longitude[1]);
}
if (longitude.length > 0) {
lotTextView.setText(longitude[0]);
}
});
// 检查定位权限
if (checkLocationPermission()) {
// 如果已经有定位权限,可以开始定位
locationMgr.startLocation();
} else {
// 如果没有定位权限,请求权限
requestLocationPermission();
}
}
private void initSensorManager() {
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
// 检查传感器是否可用
if (accelerometer != null && magnetometer != null) {
if (null == sensorEventListener) {
// 创建传感器监听器
sensorEventListener = new SensorEventListener() {
final float[] accelerometerData = new float[3];
final float[] magnetometerData = new float[3];
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor == accelerometer) {
// 平滑加速度计数据
for (int i = 0; i < event.values.length; i++) {
accelerometerData[i] = ALPHA * accelerometerData[i] + (1 - ALPHA) * event.values[i];
}
} else if (event.sensor == magnetometer) {
// 平滑磁力计数据
for (int i = 0; i < event.values.length; i++) {
magnetometerData[i] = ALPHA * magnetometerData[i] + (1 - ALPHA) * event.values[i];
}
}
// 只在两个传感器都有数据时计算方向并更新指南针
updateCompass(accelerometerData, magnetometerData);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 传感器精度改变时的处理
}
};
}
} else {
// 加速度计或磁力计不可用,进行错误处理
}
}
private void updateCompass(float[] accelerometerData, float[] magnetometerData) {
// 计算方向并更新指南针
float[] rotationMatrix = new float[9];
float[] orientationValues = new float[3];
boolean success = SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerData, magnetometerData);
if (success) {
SensorManager.getOrientation(rotationMatrix, orientationValues);
// 获取当前时间
long currentTime = System.currentTimeMillis();
// 更新指南针或其他UI元素
currentDegrees = Math.toDegrees(orientationValues[0]);
if (Math.abs(currentDegrees - lastDegrees) >= 1 && currentTime - lastUpdateTime >= 20) {
Log.e("AAAAA", " " + "degree " + currentDegrees);
runOnUiThread(runnable);
lastUpdateTime = currentTime;
// 更新上次更新时间
}
}
}
private final Runnable runnable = new Runnable() {
@Override
public void run() {
rotate(currentDegrees);
}
};
private void rotate(double currentDegrees) {
double rotation = (currentDegrees + 360) % 360;
compass.setRotation(-(float) rotation);
compress_direction.setRotation(-(float) rotation);
lastDegrees = currentDegrees;
degrees.setText(String.format("%s%s", SensorUtils.getDirectionFromAngle(currentDegrees), (int)rotation));
}
@Override
protected void onResume() {
super.onResume();
// 注册传感器监听器
if (null != accelerometer) {
sensorManager.registerListener(sensorEventListener, accelerometer, SensorManager.SENSOR_DELAY_GAME);
}
if (null != magnetometer) {
sensorManager.registerListener(sensorEventListener, magnetometer, SensorManager.SENSOR_DELAY_GAME);
}
}
@Override
protected void onPause() {
super.onPause();
if (null != sensorEventListener) {
if (null != sensorManager) {
sensorManager.unregisterListener(sensorEventListener);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != sensorEventListener) {
if (null != sensorManager) {
sensorManager.unregisterListener(sensorEventListener);
sensorEventListener = null;
}
}
}
private boolean checkLocationPermission() {
// 检查是否有定位权限
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
private void requestLocationPermission() {
// 请求定位权限
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE
);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户授予了定位权限,现在可以开始定位
locationMgr.startLocation();
} else {
// 用户拒绝了定位权限
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_CANCEL);
}
}
}
public static void instance(Context context) {
Intent intent = new Intent(context, CompassActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.calculator.activity;
import android.Manifest;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
import com.ads.cal.calculator.utils.LogUtils;
public class DecibelActivity extends BaseActivity {
private static final int AUDIO_SAMPLE_RATE = 44100;
private static final int AUDIO_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int AUDIO_ENCODING_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int PERMISSION_REQUEST_CODE = 1;
private static final int MSG_UPDATE_DECIBEL = 1;
private AudioRecord audioRecord;
private boolean isRecording = false;
private Handler handler;
private LinearLayout pointer;
private TextView decibelMinValue, decibelMaxValue, value;
private LinearLayout layout;
@Override
protected String getPageExpEventId() {
return EventIdUtils.DECIBEL_EXP;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_decibel;
}
@Override
protected void initView() {
pointer = findViewById(R.id.pointer);
value = findViewById(R.id.value);
decibelMinValue = findViewById(R.id.min_value);
decibelMaxValue = findViewById(R.id.max_value);
layout = findViewById(R.id.standardsLayout);
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == MSG_UPDATE_DECIBEL) {
double soundIntensity = (double) msg.obj;
updateDecibelUI(soundIntensity);
}
}
};
LinearLayout back = findViewById(R.id.back);
back.setOnClickListener(v -> {
if (layout.getVisibility() != View.GONE) {
layout.setVisibility(View.GONE);
}
});
findViewById(R.id.look).setOnClickListener(v -> {
if (layout.getVisibility() != View.VISIBLE) {
layout.setVisibility(View.VISIBLE);
}
});
}
private int minDecibel = 120, maxDecibel;
private boolean firstUpdateValue = true;
private void updateDecibelUI(double decibel) {
float rotation = (float) ((decibel + 120) / (300 + 120) * 300.0 - 60.0);
LogUtils.log("rotation", "rotation "+ rotation);
// 创建一个属性动画以平滑旋转视图
ObjectAnimator animator = ObjectAnimator.ofFloat(pointer, "rotation", rotation);
animator.setDuration(300); // 设置动画持续时间(毫秒)
animator.start();
decibel = 90 - Math.abs(decibel);
if (decibel > maxDecibel) {
maxDecibel = (int) decibel;
}
decibelMaxValue.setText(String.format("%s dB", maxDecibel));
value.setText(String.valueOf((int) decibel));
if (firstUpdateValue) {
firstUpdateValue = false;
return;
}
if (decibel < minDecibel) {
minDecibel = (int) decibel;
}
decibelMinValue.setText(String.format("%s dB", minDecibel));
}
@Override
protected void initData() {
startRecording();
}
private void startRecording() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
LogUtils.log("no Permission ");
return;
}
int bufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_CONFIG, AUDIO_ENCODING_FORMAT);
if (bufferSize == AudioRecord.ERROR_BAD_VALUE) {
LogUtils.log("处理错误:无效的参数 ");
// 处理错误:无效的参数
return;
}
audioRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AUDIO_ENCODING_FORMAT)
.setSampleRate(AUDIO_SAMPLE_RATE)
.setChannelMask(AUDIO_CHANNEL_CONFIG)
.build())
.setBufferSizeInBytes(bufferSize)
.build();
audioRecord.startRecording();
isRecording = true;
new Thread(() -> {
short[] buffer = new short[bufferSize];
int sampleCount = 0;
double soundIntensitySum = 0;
while (isRecording) {
audioRecord.read(buffer, 0, bufferSize);
double maxAmplitude = 0;
for (short s : buffer) {
double amplitude = Math.abs((double) s / Short.MAX_VALUE);
maxAmplitude = Math.max(maxAmplitude, amplitude);
}
soundIntensitySum += maxAmplitude * maxAmplitude;
sampleCount++;
LogUtils.log("sampleCount= "+sampleCount +" bufferSize= "+bufferSize+" "+AUDIO_SAMPLE_RATE / (bufferSize * 3));
// 每秒更新一次分贝值
if (sampleCount == AUDIO_SAMPLE_RATE / (bufferSize * 3)) {
// 计算平均声音强度的平方
double averageSoundIntensitySquared = soundIntensitySum / sampleCount;
LogUtils.log("averageSoundIntensitySquared= "+averageSoundIntensitySquared);
// 计算分贝值
double decibel = 10 * Math.log10(averageSoundIntensitySquared);
LogUtils.log("decibel= "+decibel);
// 发送分贝值到主线程更新UI
Message message = handler.obtainMessage(MSG_UPDATE_DECIBEL, decibel);
handler.sendMessage(message);
// 重置计数器和声音强度总和
sampleCount = 0;
soundIntensitySum = 0;
}
}
audioRecord.stop();
audioRecord.release();
}).start();
}
private void stopRecording() {
isRecording = false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startRecording();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopRecording();
}
@Override
public void onBackPressed() {
if (layout.getVisibility() != View.GONE) {
layout.setVisibility(View.GONE);
return;
}
super.onBackPressed();
}
public static void instance(Context context) {
Intent intent = new Intent(context, DecibelActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.calculator.activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
public class SettingActivity extends BaseActivity {
@Override
protected String getPageExpEventId() {
return EventIdUtils.SETTING_EXP;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_setting;
}
@Override
protected void initView() {
findViewById(R.id.clear_data).setOnClickListener(view -> {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
am.clearApplicationUserData();
FireBaseAnalyticsUtils.init().send(EventIdUtils.SETTING_CLEAR_DATA_CLK);
});
findViewById(R.id.about).setOnClickListener(view -> {
AboutActivity.invoke(SettingActivity.this);
FireBaseAnalyticsUtils.init().send(EventIdUtils.SETTING_ABOUT_US_CLK);
});
findViewById(R.id.privacy).setOnClickListener(view -> {
WebActivity.invoke(SettingActivity.this);
FireBaseAnalyticsUtils.init().send(EventIdUtils.SETTING_PRIVACY_CLK);
});
findViewById(R.id.back).setOnClickListener(view -> {
finish();
});
}
@Override
protected void initData() {
}
public static void invoke(Context context) {
Intent intent = new Intent(context, SettingActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.calculator.activity;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.ads.cal.calculator.MainActivity;
import com.ads.cal.calculator.MyApplication;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.ad.AppOpenAdManager;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
import com.ads.cal.calculator.utils.LogUtils;
import java.lang.ref.WeakReference;
@SuppressLint("CustomSplashScreen")
public class SplashActivity extends BaseActivity {
private ProgressBar progressBar;
private MyHandler myHandler;
private boolean isGoHome = false;
private static class MyHandler extends Handler {
private final WeakReference<Context> contextWeakReference;
private int value = 0;
public MyHandler(Context context) {
super();
contextWeakReference = new WeakReference<>(context);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Context context = contextWeakReference.get();
if (null == context) {
return;
}
if (context instanceof SplashActivity) {
if (msg.what == 1) {
if (value <= 100) {
((SplashActivity) context).setProgressValue(value++, false);
sendEmptyMessageDelayed(msg.what, 10);
}
}
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.log("application", "splash onCreate Time == "+ (System.currentTimeMillis() - MyApplication.startTime) / 1000);
}
@Override
protected String getPageExpEventId() {
return EventIdUtils.LAUNCH_EXP;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_splash;
}
@Override
protected void initView() {
progressBar = findViewById(R.id.progress_circular);
myHandler = new MyHandler(this);
}
@Override
protected void onResume() {
super.onResume();
LogUtils.log("application", "splash onResume Time == "+ (System.currentTimeMillis() - MyApplication.startTime) / 1000);
}
@Override
protected void onPause() {
super.onPause();
}
private void setProgressValue(int progress, boolean adLoadSuccess) {
if (null != progressBar) {
progressBar.setProgress(progress);
}
LogUtils.log("progress " + progress);
if (progress >= 100) {
myHandler.removeCallbacksAndMessages(null);
if (adLoadSuccess) {
return;
}
LogUtils.log("goHome ");
goHome();
}
}
private void goHome() {
if (isGoHome) {
return;
}
isGoHome = true;
// MyApplication application = MyApplication.getApplication();
// AppOpenAdManager appOpenAdManager = application.getAppOpenAdManager();
// if (null != appOpenAdManager) {
// appOpenAdManager.setOnLoadAdCompleteListener(null);
// appOpenAdManager.setOnShowAdCompleteListener(null);
// }
MainActivity.invoke(SplashActivity.this);
finish();
overridePendingTransition(0, 0);
}
@Override
protected void initData() {
// MyApplication application = MyApplication.getApplication();
// AppOpenAdManager appOpenAdManager = application.getAppOpenAdManager();
// if (null == appOpenAdManager) {
// return;
// }
// if (appOpenAdManager.isAdAvailable()) {
// FireBaseAnalyticsUtils.init().send(EventIdUtils.AD_OPEN_HAS_CACHE);
// LogUtils.log("showOpenAd");
// showOpenAd(appOpenAdManager);
// setProgressValue(100, true);
// } else {
// FireBaseAnalyticsUtils.init().send(EventIdUtils.AD_OPEN_NOT_CACHE);
// LogUtils.log("loadOpenAd");
// loadOpenAd(appOpenAdManager);
// myHandler.sendEmptyMessage(1);
// }
myHandler.sendEmptyMessage(1);
}
private void showOpenAd(AppOpenAdManager appOpenAdManager) {
appOpenAdManager.setOnShowAdCompleteListener(new AppOpenAdManager.OnShowAdCompleteListener() {
@Override
public void onShowAdComplete() {
LogUtils.log("onShowAdComplete");
goHome();
}
@Override
public void onShowAdFailed(String errorMsg) {
LogUtils.log(errorMsg);
goHome();
}
});
appOpenAdManager.showAdIfAvailable(this);
}
private void loadOpenAd(AppOpenAdManager appOpenAdManager) {
appOpenAdManager.setOnLoadAdCompleteListener(new AppOpenAdManager.OnLoadAdCompleteListener() {
@Override
public void onLoadAdComplete() {
LogUtils.log("onLoadAdComplete");
setProgressValue(100, true);
showOpenAd(appOpenAdManager);
}
@Override
public void onLoadAdFailed(String errorMsg) {
LogUtils.log(errorMsg);
setProgressValue(100, false);
}
});
appOpenAdManager.loadAd();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != myHandler) {
myHandler.removeCallbacksAndMessages(null);
}
}
@Override
public void onBackPressed() {
}
}
package com.ads.cal.calculator.activity;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.webkit.WebView;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.base.BaseActivity;
import com.ads.cal.calculator.utils.EventIdUtils;
public class WebActivity extends BaseActivity {
@Override
protected String getPageExpEventId() {
return EventIdUtils.PRIVACY_POLICY;
}
@Override
protected int getLayoutId() {
return R.layout.app_activity_web;
}
private WebView webView;
@Override
protected void initView() {
webView = findViewById(R.id.web);
webView.loadUrl("file:///android_asset/privacypolicy.html");
findViewById(R.id.back).setOnClickListener(view -> finish());
}
@Override
protected void initData() {
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.loadUrl("about:blank"); // 加载一个空白页面,释放资源
webView.clearHistory(); // 清除WebView的历史记录
webView.destroy(); // 销毁WebView
}
super.onDestroy();
}
public static void invoke(Context context) {
Intent intent = new Intent(context, WebActivity.class);
context.startActivity(intent);
}
}
package com.ads.cal.calculator.ad;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_CLK;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_CLOSE;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_EXP;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_LOAD_FAILED;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_LOAD_SUCCESS;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_SHOW_FAILED;
import static com.ads.cal.calculator.utils.EventIdUtils.AD_OPEN_START_LOAD;
import android.app.Activity;
import androidx.annotation.NonNull;
import com.ads.cal.calculator.MyApplication;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
import com.ads.cal.calculator.utils.LogUtils;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.appopen.AppOpenAd;
import java.util.Date;
public class AppOpenAdManager {
private static final String LOG_TAG = "AppOpenAdManager";
// private static final String AD_UNIT_ID = "ca-app-pub-3940256099942544/3419835294";
private AppOpenAd appOpenAd = null;
private boolean isLoadingAd = false;
private boolean isShowingAd = false;
private OnLoadAdCompleteListener onLoadAdCompleteListener;
private OnShowAdCompleteListener onShowAdCompleteListener;
public boolean isShowingAd() {
return isShowingAd;
}
/**
* Keep track of the time an app open ad is loaded to ensure you don't show an expired ad.
*/
private long loadTime = 0;
/**
* Constructor.
*/
public AppOpenAdManager() {
}
/**
* Load an ad.
*
*/
public void loadAd() {
// Do not load ad if there is an unused ad or one is already loading.
if (isLoadingAd || isAdAvailable()) {
return;
}
isLoadingAd = true;
AdRequest request = new AdRequest.Builder().build();
AppOpenAd.load(
MyApplication.getApplication(),
"",
request,
new AppOpenAd.AppOpenAdLoadCallback() {
/**
* Called when an app open ad has loaded.
*
* @param ad the loaded app open ad.
*/
@Override
public void onAdLoaded(AppOpenAd ad) {
appOpenAd = ad;
isLoadingAd = false;
loadTime = (new Date()).getTime();
if (null != onLoadAdCompleteListener) {
onLoadAdCompleteListener.onLoadAdComplete();
onLoadAdCompleteListener = null;
}
LogUtils.log(LOG_TAG, "onAdLoaded.");
FireBaseAnalyticsUtils.init().send(AD_OPEN_LOAD_SUCCESS);
}
/**
* Called when an app open ad has failed to load.
*
* @param loadAdError the error.
*/
@Override
public void onAdFailedToLoad(LoadAdError loadAdError) {
isLoadingAd = false;
if (null != onLoadAdCompleteListener) {
onLoadAdCompleteListener.onLoadAdFailed(loadAdError.getMessage());
onLoadAdCompleteListener = null;
}
LogUtils.log(LOG_TAG, "onAdFailedToLoad: " + loadAdError.getMessage());
FireBaseAnalyticsUtils.init().send(AD_OPEN_LOAD_FAILED);
}
});
FireBaseAnalyticsUtils.init().send(AD_OPEN_START_LOAD);
}
/**
* Check if ad was loaded more than n hours ago.
*/
private boolean wasLoadTimeLessThanNHoursAgo(long numHours) {
long dateDifference = (new Date()).getTime() - loadTime;
long numMilliSecondsPerHour = 3600000;
return (dateDifference < (numMilliSecondsPerHour * numHours));
}
/**
* Check if ad exists and can be shown.
*/
public boolean isAdAvailable() {
// Ad references in the app open beta will time out after four hours, but this time limit
// may change in future beta versions. For details, see:
// https://support.google.com/admob/answer/9341964?hl=en
return appOpenAd != null && wasLoadTimeLessThanNHoursAgo(4);
}
/**
* Show the ad if one isn't already showing.
*
* @param activity the activity that shows the app open ad
*/
public boolean showAdIfAvailable(
@NonNull Activity activity) {
// If the app open ad is already showing, do not show the ad again.
if (isShowingAd) {
LogUtils.log(LOG_TAG, "The app open ad is already showing.");
return false;
}
// If the app open ad is not available yet, invoke the callback then load the ad.
if (!isAdAvailable()) {
LogUtils.log(LOG_TAG, "The app open ad is not ready yet.");
loadAd();
return false;
}
LogUtils.log(LOG_TAG, "Will show ad.");
appOpenAd.setFullScreenContentCallback(
new FullScreenContentCallback() {
/** Called when full screen content is dismissed. */
@Override
public void onAdDismissedFullScreenContent() {
// Set the reference to null so isAdAvailable() returns false.
appOpenAd = null;
isShowingAd = false;
LogUtils.log(LOG_TAG, "onAdDismissedFullScreenContent.");
if (null != onShowAdCompleteListener) {
onShowAdCompleteListener.onShowAdComplete();
onShowAdCompleteListener = null;
}
loadAd();
FireBaseAnalyticsUtils.init().send(AD_OPEN_CLOSE);
}
/** Called when fullscreen content failed to show. */
@Override
public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) {
appOpenAd = null;
isShowingAd = false;
LogUtils.log(LOG_TAG, "onAdFailedToShowFullScreenContent: " + adError.getMessage());
if (null != onShowAdCompleteListener) {
onShowAdCompleteListener.onShowAdFailed(adError.getMessage());
onShowAdCompleteListener = null;
}
loadAd();
FireBaseAnalyticsUtils.init().send(AD_OPEN_SHOW_FAILED);
}
/** Called when fullscreen content is shown. */
@Override
public void onAdShowedFullScreenContent() {
LogUtils.log(LOG_TAG, "onAdShowedFullScreenContent.");
}
@Override
public void onAdImpression() {
super.onAdImpression();
LogUtils.log(LOG_TAG, "onAdImpression.");
FireBaseAnalyticsUtils.init().send(AD_OPEN_EXP);
}
@Override
public void onAdClicked() {
super.onAdClicked();
FireBaseAnalyticsUtils.init().send(AD_OPEN_CLK);
}
});
isShowingAd = true;
appOpenAd.show(activity);
return true;
}
public interface OnShowAdCompleteListener {
void onShowAdComplete();
void onShowAdFailed(String errorMsg);
}
public interface OnLoadAdCompleteListener {
void onLoadAdComplete();
void onLoadAdFailed(String errorMsg);
}
public void setOnLoadAdCompleteListener(OnLoadAdCompleteListener onLoadAdCompleteListener) {
this.onLoadAdCompleteListener = onLoadAdCompleteListener;
}
public void setOnShowAdCompleteListener(OnShowAdCompleteListener onShowAdCompleteListener) {
this.onShowAdCompleteListener = onShowAdCompleteListener;
}
}
package com.ads.cal.calculator.base;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.ads.cal.calculator.R;
import com.ads.cal.calculator.activity.AboutActivity;
import com.ads.cal.calculator.activity.SettingActivity;
import com.ads.cal.calculator.activity.SplashActivity;
import com.ads.cal.calculator.utils.FireBaseAnalyticsUtils;
public abstract class BaseActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window window = getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
window.setStatusBarColor(getColor(getStatusBarColor())); // 设置颜色
} else {
window.setStatusBarColor(getResources().getColor(getStatusBarColor())); // 设置颜色
}
setContentView(getLayoutId());
initView();
initData();
String eventId = getPageExpEventId();
if (null != eventId) {
FireBaseAnalyticsUtils.init().send(eventId);
}
}
protected abstract String getPageExpEventId();
protected abstract int getLayoutId();
protected abstract void initView();
protected abstract void initData();
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
private int getStatusBarColor() {
if (this instanceof SettingActivity
|| this instanceof AboutActivity) {
return R.color.white;
}
if (this instanceof SplashActivity) {
return R.color.app_00FFD8D8;
}
return R.color.app_f1f4f6;
}
}
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.ads.cal.calculator.nativetemplates;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import androidx.annotation.Nullable;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** A class containing the optional styling options for the Native Template. */
public class NativeTemplateStyle {
// Call to action typeface.
private Typeface callToActionTextTypeface;
// Size of call to action text.
private float callToActionTextSize;
// Call to action typeface color in the form 0xAARRGGBB.
@Nullable private Integer callToActionTypefaceColor;
// Call to action background color.
private ColorDrawable callToActionBackgroundColor;
// All templates have a primary text area which is populated by the native ad's headline.
// Primary text typeface.
private Typeface primaryTextTypeface;
// Size of primary text.
private float primaryTextSize;
// Primary text typeface color in the form 0xAARRGGBB.
@Nullable private Integer primaryTextTypefaceColor;
// Primary text background color.
private ColorDrawable primaryTextBackgroundColor;
// The typeface, typeface color, and background color for the second row of text in the template.
// All templates have a secondary text area which is populated either by the body of the ad or
// by the rating of the app.
// Secondary text typeface.
private Typeface secondaryTextTypeface;
// Size of secondary text.
private float secondaryTextSize;
// Secondary text typeface color in the form 0xAARRGGBB.
@Nullable private Integer secondaryTextTypefaceColor;
// Secondary text background color.
private ColorDrawable secondaryTextBackgroundColor;
// The typeface, typeface color, and background color for the third row of text in the template.
// The third row is used to display store name or the default tertiary text.
// Tertiary text typeface.
private Typeface tertiaryTextTypeface;
// Size of tertiary text.
private float tertiaryTextSize;
// Tertiary text typeface color in the form 0xAARRGGBB.
@Nullable private Integer tertiaryTextTypefaceColor;
// Tertiary text background color.
private ColorDrawable tertiaryTextBackgroundColor;
// The background color for the bulk of the ad.
private ColorDrawable mainBackgroundColor;
public Typeface getCallToActionTextTypeface() {
return callToActionTextTypeface;
}
public float getCallToActionTextSize() {
return callToActionTextSize;
}
@Nullable
public Integer getCallToActionTypefaceColor() {
return callToActionTypefaceColor;
}
public ColorDrawable getCallToActionBackgroundColor() {
return callToActionBackgroundColor;
}
public Typeface getPrimaryTextTypeface() {
return primaryTextTypeface;
}
public float getPrimaryTextSize() {
return primaryTextSize;
}
@Nullable
public Integer getPrimaryTextTypefaceColor() {
return primaryTextTypefaceColor;
}
public ColorDrawable getPrimaryTextBackgroundColor() {
return primaryTextBackgroundColor;
}
public Typeface getSecondaryTextTypeface() {
return secondaryTextTypeface;
}
public float getSecondaryTextSize() {
return secondaryTextSize;
}
@Nullable
public Integer getSecondaryTextTypefaceColor() {
return secondaryTextTypefaceColor;
}
public ColorDrawable getSecondaryTextBackgroundColor() {
return secondaryTextBackgroundColor;
}
public Typeface getTertiaryTextTypeface() {
return tertiaryTextTypeface;
}
public float getTertiaryTextSize() {
return tertiaryTextSize;
}
@Nullable
public Integer getTertiaryTextTypefaceColor() {
return tertiaryTextTypefaceColor;
}
public ColorDrawable getTertiaryTextBackgroundColor() {
return tertiaryTextBackgroundColor;
}
public ColorDrawable getMainBackgroundColor() {
return mainBackgroundColor;
}
/** A class that provides helper methods to build a style object. */
public static class Builder {
private NativeTemplateStyle styles;
public Builder() {
this.styles = new NativeTemplateStyle();
}
@CanIgnoreReturnValue
public Builder withCallToActionTextTypeface(Typeface callToActionTextTypeface) {
this.styles.callToActionTextTypeface = callToActionTextTypeface;
return this;
}
@CanIgnoreReturnValue
public Builder withCallToActionTextSize(float callToActionTextSize) {
this.styles.callToActionTextSize = callToActionTextSize;
return this;
}
@CanIgnoreReturnValue
public Builder withCallToActionTypefaceColor(int callToActionTypefaceColor) {
this.styles.callToActionTypefaceColor = callToActionTypefaceColor;
return this;
}
@CanIgnoreReturnValue
public Builder withCallToActionBackgroundColor(ColorDrawable callToActionBackgroundColor) {
this.styles.callToActionBackgroundColor = callToActionBackgroundColor;
return this;
}
@CanIgnoreReturnValue
public Builder withPrimaryTextTypeface(Typeface primaryTextTypeface) {
this.styles.primaryTextTypeface = primaryTextTypeface;
return this;
}
@CanIgnoreReturnValue
public Builder withPrimaryTextSize(float primaryTextSize) {
this.styles.primaryTextSize = primaryTextSize;
return this;
}
@CanIgnoreReturnValue
public Builder withPrimaryTextTypefaceColor(int primaryTextTypefaceColor) {
this.styles.primaryTextTypefaceColor = primaryTextTypefaceColor;
return this;
}
@CanIgnoreReturnValue
public Builder withPrimaryTextBackgroundColor(ColorDrawable primaryTextBackgroundColor) {
this.styles.primaryTextBackgroundColor = primaryTextBackgroundColor;
return this;
}
@CanIgnoreReturnValue
public Builder withSecondaryTextTypeface(Typeface secondaryTextTypeface) {
this.styles.secondaryTextTypeface = secondaryTextTypeface;
return this;
}
@CanIgnoreReturnValue
public Builder withSecondaryTextSize(float secondaryTextSize) {
this.styles.secondaryTextSize = secondaryTextSize;
return this;
}
@CanIgnoreReturnValue
public Builder withSecondaryTextTypefaceColor(int secondaryTextTypefaceColor) {
this.styles.secondaryTextTypefaceColor = secondaryTextTypefaceColor;
return this;
}
@CanIgnoreReturnValue
public Builder withSecondaryTextBackgroundColor(ColorDrawable secondaryTextBackgroundColor) {
this.styles.secondaryTextBackgroundColor = secondaryTextBackgroundColor;
return this;
}
@CanIgnoreReturnValue
public Builder withTertiaryTextTypeface(Typeface tertiaryTextTypeface) {
this.styles.tertiaryTextTypeface = tertiaryTextTypeface;
return this;
}
@CanIgnoreReturnValue
public Builder withTertiaryTextSize(float tertiaryTextSize) {
this.styles.tertiaryTextSize = tertiaryTextSize;
return this;
}
@CanIgnoreReturnValue
public Builder withTertiaryTextTypefaceColor(int tertiaryTextTypefaceColor) {
this.styles.tertiaryTextTypefaceColor = tertiaryTextTypefaceColor;
return this;
}
@CanIgnoreReturnValue
public Builder withTertiaryTextBackgroundColor(ColorDrawable tertiaryTextBackgroundColor) {
this.styles.tertiaryTextBackgroundColor = tertiaryTextBackgroundColor;
return this;
}
@CanIgnoreReturnValue
public Builder withMainBackgroundColor(ColorDrawable mainBackgroundColor) {
this.styles.mainBackgroundColor = mainBackgroundColor;
return this;
}
public NativeTemplateStyle build() {
return styles;
}
}
}
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.ads.cal.calculator.nativetemplates;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.ads.cal.calculator.R;
import com.google.android.gms.ads.nativead.MediaView;
import com.google.android.gms.ads.nativead.NativeAd;
import com.google.android.gms.ads.nativead.NativeAdView;
/**
* Base class for a template view. *
*/
public class TemplateView extends FrameLayout {
private int templateType;
private NativeTemplateStyle styles;
private NativeAd nativeAd;
private NativeAdView nativeAdView;
private TextView primaryView;
private TextView secondaryView;
private RatingBar ratingBar;
private TextView tertiaryView;
private ImageView iconView;
private MediaView mediaView;
private Button callToActionView;
private ConstraintLayout background;
private static final String MEDIUM_TEMPLATE = "medium_template";
private static final String SMALL_TEMPLATE = "small_template";
public TemplateView(Context context) {
super(context);
}
public TemplateView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
public TemplateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
public TemplateView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context, attrs);
}
public void setStyles(NativeTemplateStyle styles) {
this.styles = styles;
this.applyStyles();
}
public NativeAdView getNativeAdView() {
return nativeAdView;
}
private void applyStyles() {
Drawable mainBackground = styles.getMainBackgroundColor();
if (mainBackground != null) {
background.setBackground(mainBackground);
if (primaryView != null) {
primaryView.setBackground(mainBackground);
}
if (secondaryView != null) {
secondaryView.setBackground(mainBackground);
}
if (tertiaryView != null) {
tertiaryView.setBackground(mainBackground);
}
}
Typeface primary = styles.getPrimaryTextTypeface();
if (primary != null && primaryView != null) {
primaryView.setTypeface(primary);
}
Typeface secondary = styles.getSecondaryTextTypeface();
if (secondary != null && secondaryView != null) {
secondaryView.setTypeface(secondary);
}
Typeface tertiary = styles.getTertiaryTextTypeface();
if (tertiary != null && tertiaryView != null) {
tertiaryView.setTypeface(tertiary);
}
Typeface ctaTypeface = styles.getCallToActionTextTypeface();
if (ctaTypeface != null && callToActionView != null) {
callToActionView.setTypeface(ctaTypeface);
}
if (styles.getPrimaryTextTypefaceColor() != null && primaryView != null) {
primaryView.setTextColor(styles.getPrimaryTextTypefaceColor());
}
if (styles.getSecondaryTextTypefaceColor() != null && secondaryView != null) {
secondaryView.setTextColor(styles.getSecondaryTextTypefaceColor());
}
if (styles.getTertiaryTextTypefaceColor() != null && tertiaryView != null) {
tertiaryView.setTextColor(styles.getTertiaryTextTypefaceColor());
}
if (styles.getCallToActionTypefaceColor() != null && callToActionView != null) {
callToActionView.setTextColor(styles.getCallToActionTypefaceColor());
}
float ctaTextSize = styles.getCallToActionTextSize();
if (ctaTextSize > 0 && callToActionView != null) {
callToActionView.setTextSize(ctaTextSize);
}
float primaryTextSize = styles.getPrimaryTextSize();
if (primaryTextSize > 0 && primaryView != null) {
primaryView.setTextSize(primaryTextSize);
}
float secondaryTextSize = styles.getSecondaryTextSize();
if (secondaryTextSize > 0 && secondaryView != null) {
secondaryView.setTextSize(secondaryTextSize);
}
float tertiaryTextSize = styles.getTertiaryTextSize();
if (tertiaryTextSize > 0 && tertiaryView != null) {
tertiaryView.setTextSize(tertiaryTextSize);
}
Drawable ctaBackground = styles.getCallToActionBackgroundColor();
if (ctaBackground != null && callToActionView != null) {
callToActionView.setBackground(ctaBackground);
}
Drawable primaryBackground = styles.getPrimaryTextBackgroundColor();
if (primaryBackground != null && primaryView != null) {
primaryView.setBackground(primaryBackground);
}
Drawable secondaryBackground = styles.getSecondaryTextBackgroundColor();
if (secondaryBackground != null && secondaryView != null) {
secondaryView.setBackground(secondaryBackground);
}
Drawable tertiaryBackground = styles.getTertiaryTextBackgroundColor();
if (tertiaryBackground != null && tertiaryView != null) {
tertiaryView.setBackground(tertiaryBackground);
}
invalidate();
requestLayout();
}
private boolean adHasOnlyStore(NativeAd nativeAd) {
String store = nativeAd.getStore();
String advertiser = nativeAd.getAdvertiser();
return !TextUtils.isEmpty(store) && TextUtils.isEmpty(advertiser);
}
public void setNativeAd(NativeAd nativeAd) {
this.nativeAd = nativeAd;
String store = nativeAd.getStore();
String advertiser = nativeAd.getAdvertiser();
String headline = nativeAd.getHeadline();
String body = nativeAd.getBody();
String cta = nativeAd.getCallToAction();
Double starRating = nativeAd.getStarRating();
NativeAd.Image icon = nativeAd.getIcon();
String secondaryText;
nativeAdView.setCallToActionView(callToActionView);
nativeAdView.setHeadlineView(primaryView);
nativeAdView.setMediaView(mediaView);
secondaryView.setVisibility(VISIBLE);
if (adHasOnlyStore(nativeAd)) {
nativeAdView.setStoreView(secondaryView);
secondaryText = store;
} else if (!TextUtils.isEmpty(advertiser)) {
nativeAdView.setAdvertiserView(secondaryView);
secondaryText = advertiser;
} else {
secondaryText = "";
}
primaryView.setText(headline);
callToActionView.setText(cta);
// Set the secondary view to be the star rating if available.
if (starRating != null && starRating > 0) {
secondaryView.setVisibility(GONE);
ratingBar.setVisibility(VISIBLE);
ratingBar.setRating(starRating.floatValue());
nativeAdView.setStarRatingView(ratingBar);
} else {
secondaryView.setText(secondaryText);
secondaryView.setVisibility(VISIBLE);
ratingBar.setVisibility(GONE);
}
if (icon != null) {
iconView.setVisibility(VISIBLE);
iconView.setImageDrawable(icon.getDrawable());
} else {
iconView.setVisibility(GONE);
}
if (tertiaryView != null) {
tertiaryView.setText(body);
nativeAdView.setBodyView(tertiaryView);
}
nativeAdView.setNativeAd(nativeAd);
}
/**
* To prevent memory leaks, make sure to destroy your ad when you don't need it anymore. This
* method does not destroy the template view.
* https://developers.google.com/admob/android/native-unified#destroy_ad
*/
public void destroyNativeAd() {
nativeAd.destroy();
}
public String getTemplateTypeName() {
if (templateType == R.layout.gnt_medium_template_view) {
return MEDIUM_TEMPLATE;
} else if (templateType == R.layout.gnt_small_template_view) {
return SMALL_TEMPLATE;
}
return "";
}
private void initView(Context context, AttributeSet attributeSet) {
TypedArray attributes =
context.getTheme().obtainStyledAttributes(attributeSet, R.styleable.TemplateView, 0, 0);
try {
templateType =
attributes.getResourceId(
R.styleable.TemplateView_gnt_template_type, R.layout.gnt_medium_template_view);
} finally {
attributes.recycle();
}
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(templateType, this);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
nativeAdView = (NativeAdView) findViewById(R.id.native_ad_view);
primaryView = (TextView) findViewById(R.id.primary);
secondaryView = (TextView) findViewById(R.id.secondary);
tertiaryView = (TextView) findViewById(R.id.body);
ratingBar = (RatingBar) findViewById(R.id.rating_bar);
ratingBar.setEnabled(false);
callToActionView = (Button) findViewById(R.id.cta);
iconView = (ImageView) findViewById(R.id.icon);
mediaView = (MediaView) findViewById(R.id.media_view);
background = (ConstraintLayout) findViewById(R.id.background);
}
}
package com.ads.cal.calculator.utils;
import android.util.Log;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CalculatorUtils {
public static String replaceAllString(String s) {
if (null != s) {
s = s.replaceAll("×", "*");
s = s.replaceAll("÷", "/");
s = s.replaceAll("√", "sqrt");
}
return s;
// 我有一个数学表达式的字符串 他是不正确的
// 5+6×(√7÷(3-2+cos(9+sin(10-1
// 请帮我写一个方法把他变成正确的表达式 要符合EvalEx库的标准
}
public static String fixMathExpression(String input) {
// 移除不必要的空格
input = input.replaceAll("\\s+", "");
// 替换特殊符号
input = input.replace("×", "*"); // 替换乘号
input = input.replace("÷", "/"); // 替换除号
input = input.replaceAll("√(\\d+)", "sqrt($1)");
input = input.replaceAll("(\\d)%", "($1*0.01)");
input = input.replaceAll("π", "3.1415926");
// 添加缺失的函数括号
input = input.replaceAll("(sin|cos|tan|sqrt)([^\\(])", "$1($2)");
// 添加缺失的乘法操作符
input = input.replaceAll("(\\d)([a-zA-Z\\(])", "$1*$2");
input = input.replaceAll("([\\)a-zA-Z])(\\d)", "$1*$2");
// 在)和cos之间添加乘号
input = input.replaceAll("\\)(sin|cos|tan|sqrt)", ")*$1");
// 修复不正确的括号
int openParenthesesCount = input.length() - input.replace("(", "").length();
int closeParenthesesCount = input.length() - input.replace(")", "").length();
int missingParentheses = openParenthesesCount - closeParenthesesCount;
for (int i = 0; i < missingParentheses; i++) {
input += ")";
}
// 补全不规范的数学表达式
input = input.replaceAll("([0-9\\)])\\(", "$1*(");
input = input.replaceAll("\\)([0-9\\(])", ")*$1");
return input;
}
enum Operator {
ADD('+'),
SUB('-'),
MUL('×'),
DIV('÷'),
POW('^'),
PERCENT('%'),
SQRT('√');
private final char symbol;
Operator(char symbol) {
this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
}
private static final Map<Operator, Integer> OPERATOR_PRECEDENCE = Map.of(
Operator.ADD, 1,
Operator.SUB, 1,
Operator.MUL, 2,
Operator.DIV, 2,
Operator.POW, 3,
Operator.SQRT, 3,
Operator.PERCENT, 3
);
public static String evaluateExpression(String expression) {
if (null == expression || expression.length() == 0) {
return "0";
}
expression = expression.replaceAll("\\s", ""); // 移除空白字符
char[] tokens = expression.toCharArray();
Stack<Double> values = new Stack<>();
Stack<Operator> operators = new Stack<>();
for (int i = tokens.length - 1; i >= 0; i--) {
if (Character.isDigit(tokens[i])) {
StringBuilder sb = new StringBuilder();
while (i >= 0 && i < tokens.length && (Character.isDigit(tokens[i]) || tokens[i] == '.')) {
sb.append(tokens[i]);
i--;
}
values.push(Double.parseDouble(sb.reverse().toString()));
i++;
} else if (tokens[i] == '(') {
operators.push(Operator.ADD); // 添加一个虚拟的+操作符,以处理负数表达式
operators.push(Operator.SUB); // 添加一个虚拟的-操作符,以处理负数表达式
} else if (tokens[i] == ')') {
if (!operators.isEmpty()) {
Operator topOperator = operators.peek();
if (null != topOperator) {
while (topOperator != Operator.ADD && topOperator != Operator.SUB) {
Double operand1 = null;
Double operand2 = null;
if (!values.isEmpty()) {
operand1 = values.pop();
}
if (!values.isEmpty()) {
operand2 = values.pop();
}
if (!operators.isEmpty()) {
topOperator = operators.pop();
}
if (topOperator != null && operand2 != null && operand1 != null) {
values.push(applyOperator(topOperator, operand1, operand2));
}
}
if (!operators.isEmpty()) {
operators.pop(); // 弹出虚拟的+操作符
}
if (!operators.isEmpty()) {
operators.pop(); // 弹出虚拟的-操作符
}
}
}
} else if (isOperator(tokens[i])) {
Operator currentOperator = getOperatorBySymbol(tokens[i]);
Double operand1 = null;
Double operand2 = null;
while (!operators.isEmpty() && hasHigherPrecedence(currentOperator, operators.peek())) {
Operator topOperator = operators.pop();
if (!values.isEmpty()) {
operand1 = values.pop();
if (isSimpleOperator(topOperator.getSymbol())) {
values.push(applyOperator(topOperator, operand1, 0));
continue;
}
}
if (!values.isEmpty()) {
operand2 = values.pop();
}
if (operand2 != null && operand1 != null) {
values.push(applyOperator(topOperator, operand1, operand2));
}
}
if (values.isEmpty() && null != operand1) {
values.push(operand1);
}
operators.push(currentOperator);
}
}
if (operators.isEmpty()) {
return "";
}
String result = "";
// 计算并返回最终结果
while (!operators.isEmpty()) {
Operator ch1 = operators.pop();
Double operand1 = null, operand2 = null;
if (!values.isEmpty()) {
operand1 = values.pop();
result = String.valueOf(operand1);
}
if (isSimpleOperator(ch1.symbol) && null != operand1) {
values.push(applyOperator(ch1, operand1, 0));
continue;
}
if (!values.isEmpty()) {
operand2 = values.pop();
}
if (null != operand1 && null != operand2) {
values.push(applyOperator(ch1, operand1, operand2));
}
}
if (!values.isEmpty()) {
return String.valueOf(values.pop());
} else {
return result;
}
}
public static boolean isOperator(char c) {
return c == '+' || c == '-' || c == '×' || c == '÷' || c == '^' || c == '√' || c == '%';
}
private static boolean isSimpleOperator(char c) {
return c == '%' || c == '√';
}
private static Operator getOperatorBySymbol(char symbol) {
for (Operator op : Operator.values()) {
if (op.getSymbol() == symbol) {
return op;
}
}
throw new IllegalArgumentException("Invalid operator: " + symbol);
}
private static boolean hasHigherPrecedence(Operator op1, Operator op2) {
return OPERATOR_PRECEDENCE.get(op2) > OPERATOR_PRECEDENCE.get(op1);
}
private static double applyOperator(Operator operator, double operand1, double operand2) {
switch (operator) {
case ADD:
return operand1 + operand2;
case SUB:
return operand1 - operand2;
case MUL:
return operand1 * operand2;
case DIV:
if (operand2 == 0) {
return Double.NaN;
}
return operand1 / operand2;
case POW:
return Math.pow(operand1, operand2);
case SQRT:
return Math.sqrt(operand2);
case PERCENT:
return operand1 / 100;
default:
throw new IllegalArgumentException("Invalid operator: " + operator);
}
}
}
package com.ads.cal.calculator.utils;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.ads.cal.calculator.activity.CompassActivity;
public class CustomCompassView extends View {
private Paint paint;
private float azimuth = 0;
public CustomCompassView(Context context) {
super(context);
init();
}
public CustomCompassView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomCompassView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CustomCompassView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(24);
paint.setColor(Color.BLACK);
}
public void setAzimuth(float azimuth) {
this.azimuth = azimuth;
invalidate(); // 通知View重新绘制
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int centerX = width / 2;
int centerY = height / 2;
int radius = 200; // 指南针的半径
// 绘制指南针外圆
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(centerX, centerY, radius, paint);
// 绘制指南针文字
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
canvas.drawText("N", centerX - 10, centerY - radius + 30, paint);
canvas.drawText("S", centerX - 10, centerY + radius - 10, paint);
canvas.drawText("E", centerX + radius - 30, centerY + 10, paint);
canvas.drawText("W", centerX - radius + 10, centerY + 10, paint);
// 绘制刻度和角度
paint.setColor(Color.BLACK);
paint.setTextSize(20);
paint.setStyle(Paint.Style.FILL);
for (int i = 0; i < 360; i += 30) {
double angle = Math.toRadians(-i);
float startX = centerX + (float) (radius * Math.sin(angle));
float startY = centerY - (float) (radius * Math.cos(angle));
float endX = centerX + (float) ((radius - 20) * Math.sin(angle));
float endY = centerY - (float) ((radius - 20) * Math.cos(angle));
// 绘制刻度线
canvas.drawLine(startX, startY, endX, endY, paint);
// 绘制角度文本
String angleText = String.valueOf(i) + "°";
float textWidth = paint.measureText(angleText);
canvas.drawText(angleText, centerX - textWidth / 2, centerY - radius + 50, paint);
}
// 绘制指南针指针
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
canvas.drawLine(centerX, centerY, centerX + (float) (100 * Math.sin(Math.toRadians(-azimuth))), centerY - (float) (100 * Math.cos(Math.toRadians(-azimuth))), paint);
}
}
package com.ads.cal.calculator.utils;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
public class DeviceUtil {
public static int getScreenWidth(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager != null) {
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels;
} else {
return 0; // 处理 WindowManager 为空的情况
}
}
}
package com.ads.cal.calculator.utils;
public class EventIdUtils {
public static final String LAUNCH_EXP = "launch_exp";
public static final String HOME_EXP = "home_exp";
public static final String CALCULATOR_EXP = "calculator_exp";
public static final String COMPASS_EXP = "compass_exp";
public static final String DECIBEL_EXP = "decibel_exp";
public static final String HOT_START = "hot_start_up";
public static final String SETTING_EXP = "setting_exp";
public static final String ABOUT_US_EXP = "about_us_exp";
public static final String PRIVACY_POLICY = "privacy_policy_exp";
public static final String CALCULATOR_HISTORY_CLK = "calculator_history_clk";
public static final String CALCULATOR_STANDARD_CLK = "calculator_standard_clk";
public static final String CALCULATOR_SCIENCE_CLK = "calculator_science_clk";
public static final String COMPASS_LOCATION_START = "compass_location_start";
public static final String COMPASS_LOCATION_ENABLE = "compass_location_enable";
public static final String COMPASS_LOCATION_SUCCESS = "compass_location_success";
public static final String COMPASS_LOCATION_FAILED = "compass_location_failed";
public static final String COMPASS_LOCATION_CANCEL = "compass_location_cancel";
public static final String HOME_CALCULATOR_CLK = "home_calculator_clk";
public static final String HOME_COMPASS_CLK = "home_compass_clk";
public static final String HOME_DECIBEL_CLK = "home_decibel_clk";
public static final String HOME_SETTING_CLK = "home_setting_clk";
public static final String SETTING_CLEAR_DATA_CLK = "setting_clear_data_clk";
public static final String SETTING_ABOUT_US_CLK = "setting_about_us_clk";
public static final String SETTING_PRIVACY_CLK = "setting_privacy_clk";
public static final String AD_OPEN_START_LOAD = "ad_open_start_load";
public static final String AD_OPEN_LOAD_SUCCESS = "ad_open_load_success";
public static final String AD_OPEN_LOAD_FAILED = "ad_open_load_failed";
public static final String AD_OPEN_HAS_CACHE = "ad_open_has_cache";
public static final String AD_OPEN_NOT_CACHE = "ad_open_not_cache";
public static final String AD_OPEN_SHOW_FAILED = "ad_open_show_failed";
public static final String AD_OPEN_EXP = "ad_open_exp";
public static final String AD_OPEN_CLK = "ad_open_clk";
public static final String AD_OPEN_CLOSE = "ad_open_close";
public static final String AD_HOME_NATIVE_START_LOAD = "ad_home_native_start_load";
public static final String AD_HOME_NATIVE_LOAD_SUCCESS = "ad_home_native_load_success";
public static final String AD_HOME_NATIVE_LOAD_FAILED = "ad_home_native_load_failed";
public static final String AD_HOME_NATIVE_EXP = "ad_home_native_exp";
public static final String AD_HOME_NATIVE_CLOSE = "ad_home_native_close";
public static final String AD_HOME_NATIVE_CLK = "ad_home_native_clk";
}
package com.ads.cal.calculator.utils;
import android.app.Application;
import android.os.Bundle;
import com.ads.cal.calculator.MyApplication;
import com.google.firebase.analytics.FirebaseAnalytics;
public class FireBaseAnalyticsUtils {
private final FirebaseAnalytics mFirebaseAnalytics;
private static volatile FireBaseAnalyticsUtils fireBaseAnalyticsUtils;
private FireBaseAnalyticsUtils(Application application) {
mFirebaseAnalytics = FirebaseAnalytics.getInstance(application);
}
public static FireBaseAnalyticsUtils init() {
if (fireBaseAnalyticsUtils == null) {
synchronized (FireBaseAnalyticsUtils.class) {
if (fireBaseAnalyticsUtils == null) {
fireBaseAnalyticsUtils = new FireBaseAnalyticsUtils(MyApplication.getApplication());
}
}
}
return fireBaseAnalyticsUtils;
}
public void send(String eventId, Bundle bundle) {
if (mFirebaseAnalytics == null) {
LogUtils.log("mFirebaseAnalytics == null " +eventId);
return;
}
LogUtils.log("eventId " +eventId);
mFirebaseAnalytics.logEvent(eventId, bundle);
}
public void send(String eventId) {
send(eventId, new Bundle());
}
}
package com.ads.cal.calculator.utils;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.core.app.ActivityCompat;
import com.ads.cal.calculator.MyApplication;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class LocationMgr {
private LocationManager locationManager;
private List<LocationListener> locationListeners; // 存储监听器列表
private final LocationCallback locationCallback;
private boolean isRequestingLocation;
private final Handler handler = new Handler(Looper.getMainLooper());
public LocationMgr(LocationCallback callback) {
this.locationCallback = callback;
locationListeners = new ArrayList<>(); // 初始化监听器列表
}
public void startLocation() {
if (ActivityCompat.checkSelfPermission(MyApplication.getApplication(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(MyApplication.getApplication(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 如果没有定位权限,请求权限
// 此处可以添加代码来请求权限
return;
}
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_START);
locationManager = (LocationManager) MyApplication.getApplication().getSystemService(Context.LOCATION_SERVICE);
List<String> list = locationManager.getProviders(true);
if (list == null || list.isEmpty()) {
// 没有可用的定位提供程序
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_ENABLE);
return;
}
isRequestingLocation = true;
// 停止之前的监听器
stopPreviousLocationUpdates();
for (int i = 0; i < list.size(); i++) {
Location location = locationManager.getLastKnownLocation(list.get(i));
if (location != null) {
updateLocation(location);
break;
}
LocationListener locationListener = getLocationListener();
locationListeners.add(locationListener); // 将监听器添加到列表
locationManager.requestSingleUpdate(list.get(i), locationListener, null);
}
// 使用Handler来延迟停止定位请求
handler.postDelayed(new Runnable() {
@Override
public void run() {
stopLocationUpdates();
}
}, 12000); // 12秒
}
private void stopPreviousLocationUpdates() {
if (!locationListeners.isEmpty()) {
for (LocationListener listener : locationListeners) {
locationManager.removeUpdates(listener); // 停止每个监听器
}
locationListeners.clear(); // 清空监听器列表
}
}
public void stopLocationUpdates() {
if (isRequestingLocation) {
stopPreviousLocationUpdates(); // 停止之前的监听器
isRequestingLocation = false;
}
}
private LocationListener getLocationListener() {
return new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
stopLocationUpdates();
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_FAILED);
}
@Override
public void onLocationChanged(Location location) {
stopLocationUpdates();
updateLocation(location);
}
};
}
private void updateLocation(Location location) {
if (location != null) {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
// 将经纬度格式化为度分秒
String[] formattedLatitude = convertToDMS(latitude, true);
String[] formattedLongitude = convertToDMS(longitude, false);
// 回调接口,将定位数据传递给其他地方
locationCallback.onLocationReceived(formattedLatitude, formattedLongitude);
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_SUCCESS);
} else {
FireBaseAnalyticsUtils.init().send(EventIdUtils.COMPASS_LOCATION_FAILED);
// 定位失败,也需要释放监听
stopLocationUpdates();
}
}
// 将经纬度格式化为度分秒
private String[] convertToDMS(double value, boolean isLatitude) {
int degrees = (int) value;
value = (value - degrees) * 60;
int minutes = (int) value;
value = (value - minutes) * 60;
double seconds = value;
String direction = isLatitude ? (value < 0 ? "南纬" : "北纬") : (value < 0 ? "西经" : "东经");
DecimalFormat decimalFormat = new DecimalFormat("00");
return new String[]{direction, degrees + "°" + decimalFormat.format(minutes) + "′" + decimalFormat.format(seconds) + "″"};
}
public interface LocationCallback {
void onLocationReceived(String[] latitude, String[] longitude);
}
}
\ No newline at end of file
package com.ads.cal.calculator.utils;
import android.util.Log;
public class LogUtils {
private static boolean consoleLoggingEnabled = true;
public static void setConsoleLoggingEnabled(boolean enabled) {
consoleLoggingEnabled = enabled;
}
public static void log(String message) {
log("AAAAA", message);
}
public static void log(String tag, String message) {
if (consoleLoggingEnabled) {
printToConsole(tag, message);
}
}
private static void printToConsole(String tag, String logMessage) {
Log.d(tag, logMessage);
}
}
package com.ads.cal.calculator.utils;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class SensorUtils {
public static String getDirectionFromAngle(double angle) {
// 将角度规范化到0到360度之间
angle = (angle % 360 + 360) % 360;
// 定义方向的范围
if (angle >= 68 && angle < 113) {
return "东";
} else if (angle >= 23 && angle < 68) {
return "东北";
} else if (angle >= 113 && angle < 157) {
return "东南";
} else if (angle >= 157 && angle < 203) {
return "南";
} else if (angle >= 203 && angle < 248) {
return "西南";
} else if (angle >= 248 && angle < 293) {
return "西";
} else if (angle >= 293 && angle < 338) {
return "西北";
} else {
return "北";
}
}
}
package com.ads.cal.calculator.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class SpUtils {
private static final String PREFERENCES_NAME = "Tools";
// 保存字符串
public static void saveString(Context context, String key, String value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
// 读取字符串
public static String getString(Context context, String key, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getString(key, defaultValue);
}
// 保存整数
public static void saveInt(Context context, String key, int value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(key, value);
editor.apply();
}
// 读取整数
public static int getInt(Context context, String key, int defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getInt(key, defaultValue);
}
// 保存布尔值
public static void saveBoolean(Context context, String key, boolean value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(key, value);
editor.apply();
}
// 读取布尔值
public static boolean getBoolean(Context context, String key, boolean defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getBoolean(key, defaultValue);
}
// 删除特定键的值
public static void removeKey(Context context, String key) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(key);
editor.apply();
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment