Log file switch(Private strand flush not complete)

当数据库在业务高峰期出现日志切换等待事件时,分析了原因并提供了通过增大日志组大小来解决该问题的方法。

同事说,在业务高峰期,数据库出现Log file switch(Private strand flush not complete)

AWR可以看到

2Q==

 

9k=

 

每秒日志量接近2M,每个事物日志接近2.6MTOP里面出现

Log file switch(Private strand flush not complete)

 

这个的原因是:

When you switch logs, all private strands have to be flushed to the current log before the switch is allowed to proceed.

 

当你做日志切换时,所有的private strands需要被刷新到当前日志组 ,才可以做切换动作.

 

我做了如下操作模拟这个等待事件:

y9vJussCv8YAAAAASUVORK5CYII=

 

从告警日志可以看到如下信息:

 

Wed Jun 01 16:47:41 2016

Thread 1 cannot allocate new log, sequence 31

Private strand flush not complete

  Current log# 2 seq# 30 mem# 0: D:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO02.LOG

Thread 1 advanced to log sequence 31

  Current log# 3 seq# 31 mem# 0: D:\ORACLE\PRODUCT\10.2.0\ORADATA\ORCL\REDO03.LOG

 

 

解决办法:可以把日志组加大到2G一个,避免切换频繁。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31134212/viewspace-2112423/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31134212/viewspace-2112423/

/******************************************************************************** ** Copyright (C), 2021-2121, Oplus Mobile Comm Corp., Ltd ** All rights reserved. ** ** File: - OplusNewFeaturesDisplayingManager.java ** Description: ** Implementation of new system features displaying manager. ** ** This service is used to: ** 1. display new system features if Android major version upgraded, e.g. R -> S ** 2. display new policies for lite boot reg requirement if OS version upgraded, ** e.g. OS V12.0 -> OS V12.1, OS V12.1 -> OS V13. See feature 3142150. ** ** Therefore, there are totally 4 cases as below: ** 1. Both Android major version and OS version upgraded: show new system features and ** new policies in boot reg. ** 2. Android major version upgraded only: show new system features. E.g. R+12 -> S+12 ** 3. OS version upgraded only: show new policies. ** 4. Neither of them are upgraded: show nothing, i.e. launch home directly. ** ** Version: 2.0 ** Date: 2022-02-24 ** Author: matt.yu@ANDROID.PMS ** TAG: OPLUS_FEATURE_NEW_FEATURES_DISPLAYING_MANAGER ** ** ------------------------------- Revision History: ---------------------------- ** <author> <date> <version> <desc> ** ------------------------------------------------------------------------------ ** matt.yu@ANDROID.PMS 2021-07-21 1.0 Create this module ** matt.yu@ANDROID.PMS 2022-02-24 2.0 Support lite boot reg requirement ********************************************************************************/ package com.android.server.oplus.nfdm; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.server.pm.OplusPackageTransformHelper.PKG_BOOTREG; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManager; import com.android.server.am.ProcessRecord; import android.annotation.NonNull; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.common.OplusFeatureCache; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ResolveInfoFlags; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.os.Binder; import android.os.Build; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.Parcelable; import android.os.Process; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import android.util.Xml; import com.android.internal.util.XmlUtils; import com.android.server.ServiceThread; import com.android.server.content.IOplusFeatureConfigManagerInternal; import com.android.server.pm.IOplusPackageManagerServiceEx; import com.android.server.pm.IOplusPmsSupportedFunctionManager; import com.android.server.pm.PackageManagerService; import com.android.server.OplusBackgroundThread; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import com.google.android.collect.Sets; import com.oplus.ota.OplusSystemUpdateInfo; import org.json.JSONException; import org.json.JSONObject; public class OplusNewFeaturesDisplayingManager implements IOplusNewFeaturesDisplayingManager { private static final String TAG = "OplusNewFeaturesDisplayingManager"; public static final String PROC_MEMINFO_PATH = "/proc/meminfo"; public static final String LABEL_MEMTOTAL = "MemTotal:"; private static final String OTA_SWITCH_RECORD_FILE_PATH = "/data/oplus/os/ota_switch_record.xml"; private static final String PROP_MARK_LAST_UPGRADED_VERSION = "persist.sys.mark_last_upgraded_version"; private static final String EXTRA_OTA_SWITCH_LIST_FROM_SYSTEM = "extra_ota_switch_list_from_system"; private static final String PROP_OTA_VERSION = "ro.build.version.ota"; private static final List<Integer> DEF_RAM_SIZE = Arrays.asList(2, 3, 4, 6, 8, 12, 16, 18, 24); private static final List<Integer> SUPPORTED_OTA_SWITCH_TARGET_SDKS = Arrays.asList( Build.VERSION_CODES.S, Build.VERSION_CODES.TIRAMISU, Build.VERSION_CODES.UPSIDE_DOWN_CAKE ); private static final long MB_IN_KB = 1024L; private static final long GB_IN_KB = MB_IN_KB * 1024L; private static final int EIGHT_GB = 8; private static int sTotalProcMemInfoGB = -1; private static int sTotalProcMemInfoKB = -1; private static final String FEATURE_TABLET = "oplus.hardware.type.tablet"; private static final String FEATURE_SCREEN_FOLDABLE = "oplus.hardware.type.fold"; private static final String FEATURE_FOLD_REMAP_DISPLAY_DISABLED = "oplus.software.fold_remap_display_disabled"; private static final List<Integer> SUPPORTED_TARGET_SDKS = Arrays.asList( Build.VERSION_CODES.S, Build.VERSION_CODES.TIRAMISU, Build.VERSION_CODES.UPSIDE_DOWN_CAKE, Build.VERSION_CODES.VANILLA_ICE_CREAM, Build.VERSION_CODES.BAKLAVA ); private static final List<Integer> SUPPORTED_WALLPAPER_TARGET_SDKS = Arrays.asList( Build.VERSION_CODES.VANILLA_ICE_CREAM ); private static final Set<String> NOT_MARKET_LIVE_WALLPAPER = Sets.newHashSet( "default_live_wp_package_name" ); private static final String PKG_NAME_BOOT_REG = PKG_BOOTREG; private static final String ACTION_WELCOME_PAGE = "com.oplus.bootreg.activity.WelcomePage"; private static final String PKG_NAME_WALLPAPER = "com.oplus.wallpapers"; private static final String ACTION_WALLPAPER_GUIDE_PAGE = "android.intent.action.THEME_INSPIRATION_BOOT"; private static final String PKG_NAME_UPGRADE_GUIDE = "com.oplus.upgradeguide"; private static final String ACTION_UPGRADE_SERVICE = "oplus.intent.upgrade.UNZIP_RESOURCE"; private static final String EXTRA_START_FROM_SYSTEM = "extra_start_from_system"; private static final String PKG_NAME_OPPO_DEFAULT_LAUNCHER = "com.android.launcher"; private static final String LAUNCHER_ACTION = "android.intent.action.MAIN"; private static final String LAUNCHER_CATEGORY = "android.intent.category.HOME"; private static final long THIRD_FLAG = 0x01; private static final int CUSTOM_THEME_FLAG = 0x100; private static final String CLS_NAME_OTA_GUIDE_PAGE = "com.oplus.upgrade.ota.ui.OtaGuidePage"; private static final String PROP_RO_SEPARATE_SOFT = "ro.separate.soft"; /** * @deprecated * Use action intent filter instead, see {@link #getBootRegWelcomePageComponentName()} */ private static final String CLS_NAME_WELCOME_PAGE = PKG_BOOTREG.concat(".settings.activity.WelcomePage"); private static final ComponentName COMPONENT_WELCOME_PAGE = new ComponentName(PKG_NAME_BOOT_REG, CLS_NAME_WELCOME_PAGE); /** * Deprecated for what's new 2.0 requirement. */ private static final String CLS_NAME_NEW_FEATURE_PAGE = PKG_NAME_BOOT_REG.concat(".activity.NewFeaturePage"); private static final ComponentName COMPONENT_NEW_FEATURE_PAGE = new ComponentName( PKG_NAME_BOOT_REG, CLS_NAME_NEW_FEATURE_PAGE); private static final String EXTRA_MAJOR_VERSION_UPGRADED = "extra_major_version_upgraded"; private static final String EXTRA_MAJOR_WALLPAPER_CHANGED = "extra_major_wallpaper_changed"; private static final String PROP_FORCE_TO_DISPLAY_NEW_FEATURES = "persist.sys.force_to_display_new_features"; private static final String PROP_MARK_TO_DISPLAY_NEW_FEATURES = "persist.sys.mark_to_display_new_features"; private static final String EXTRA_OS_VERSION_UPGRADED = "extra_os_version_upgraded"; private static final String PROP_FORCE_TO_DISPLAY_NEW_POLICIES = "persist.sys.force_to_display_new_policies"; private static final String PROP_MARK_TO_DISPLAY_NEW_POLICIES = "persist.sys.mark_to_display_new_policies"; private static final String PROP_FORCE_TO_DISPLAY_BOOTREG = "persist.sys.force_to_display_bootreg"; private static final String PROP_FORCE_TO_CHANGE_WALLPAPER = "persist.sys.force_to_change_wallpaper"; private static final String OS_VERSION_INFO_FILE_PATH = "/data/oplus/os/os_version_info.xml"; private static final String PROP_CUR_OS_VERSION = "ro.build.version.oplusrom"; private static final String PROP_STOCK_TYPE = "ro.oplus.image.my_stock.type"; private static final String PROP_CARRRIER = "ro.oplus.pipeline.carrier"; private static final String PROP_THEME_UUID = "persist.sys.oppo.theme_uuid"; private static final String PROP_COMPANY_TYPE = "ro.oplus.image.my_company.type"; private static final String PROP_CHANNEL = "persist.sys.channel.info"; private static final String DELIMITER = "_"; private static final String KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP = "oplus_upgrade_ota_step"; private static final String TAG_OS_VERSION = "os-version"; private static final String TAG_PREV_OS_VERSION = "prev-os-version"; private static final String ATTR_VALUE = "value"; private static final int S_OS_VERSION_LENGTH = 2; private static final int T_OS_VERSION_LENGTH = 3; private static final int MSG_WSN_WRITEOS = 120; private static final int MSG_WSN_EXCEPTION_NEWFEATURE = 121; private static final int MSG_WSN_STARTHOME = 122; private static final int DELAY_EXCEPTION_NEWFEATURE_SENDING_MSEC = 5000; private static final int MSG_DELAY_TO_CHECK_IF_OTA_GUIDE_ABNORMAL = 123; private static final int MSG_DELAY_TO_CHECK_STATUS_WHEN_OTA_GUIDE_EXIT = 124; private static final int DELAY_EXCEPTION_OTA_GUIDE_MSEC = 30000; private static final int DELAY_CHECK_IF_OTA_GUIDE_NORMAL_EXIT_MSEC = 2000; private static final int VALUE_OTA_GUIDE_DEFAULT = -1; private static final int VALUE_OTA_GUIDE_OVERTIME_WAIT = 0; private static final int VALUE_OTA_GUIDE_ENTER = 1; private static final int VALUE_OTA_GUIDE_EXIT = 2; private static final int TYPE_SAU_UPDATE = 2; private static final String PROP_OPLUS_CLOSE_OTA_GUIDE_PAGE = "persist.sys.oplus_close_ota_guide"; private ComponentName mComponentOtaGuidePage = null; private ContentObserver mOtaGuideContentObserver = null; private ComponentName mComponentNameWelcomePage = null; private ComponentName mComponentNameWallPaperGuidPage = null; private boolean mInitializedSuccessfully; private Context mContext; private PackageManagerService mPms; private Handler mHandler; private RunningAppProcessInfo mWsnInfo = null; private String mStockType; private boolean mIsDomesticBuild; private boolean mIsBrandSupported; private boolean mIsDeviceTablet; private boolean mIsScreenFoldable; private boolean mIsFlipDevice; private boolean mIsFirstBoot; private boolean mOtaUpgraded; private boolean mIsBootFromOTA; private int mPrevSdkVersion; private int mCurSdkVersion; private boolean mMajorVersionUpgraded; private String mProjectNumber; private List<OtaGuideBean> mOtaGuideBeans; private String mPrevOsVersion; private String mCurOsVersion; private boolean mOsVersionUpgraded; private boolean mTopIsWsn; private boolean mTopIsLauncher; private boolean mPictorialAuto; private boolean mKeKeMarketTheme; private boolean mChangeWallPaper; private boolean mIsNeedDisplayWallpaper; private boolean mIsNeedDisplaySwitchPage; private boolean mDebug = SystemProperties.getBoolean("persist.sys.assert.panic", false); private static final class InstanceHolder { private static final OplusNewFeaturesDisplayingManager INSTANCE = new OplusNewFeaturesDisplayingManager(); } public static OplusNewFeaturesDisplayingManager getInstance() { return OplusNewFeaturesDisplayingManager.InstanceHolder.INSTANCE; } private OplusNewFeaturesDisplayingManager() { // hidden constructor } @Override public void init(IOplusPackageManagerServiceEx pmsEx) { if (pmsEx == null || pmsEx.getPackageManagerService() == null) { Slog.e(TAG, "Init failed due to null pms reference"); return; } mContext = pmsEx.getContext(); mPms = pmsEx.getPackageManagerService(); mInitializedSuccessfully = true; mTopIsWsn = false ; mTopIsLauncher = false ; mIsNeedDisplayWallpaper = false; mIsNeedDisplaySwitchPage = false; initBuildTypes(); readPrevOsVersionFromXml(); mCurOsVersion = SystemProperties.get(PROP_CUR_OS_VERSION, ""); Slog.i(TAG, "prevOsVersion=" + mPrevOsVersion + ", curOsVersion=" + mCurOsVersion); HandlerThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/); thread.start(); mHandler = new Handler(thread.getLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case MSG_WSN_WRITEOS: writeOsVersionToXmlSync(); break ; case MSG_WSN_EXCEPTION_NEWFEATURE: if (!mTopIsWsn) { Slog.i(TAG, "upgradeguide can not start so direct go to home"); startHome(); ActivityManager am = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE); am.killBackgroundProcesses(PKG_NAME_UPGRADE_GUIDE); } break ; case MSG_WSN_STARTHOME: startHome(); break ; case MSG_DELAY_TO_CHECK_IF_OTA_GUIDE_ABNORMAL: ContentResolver resolver = mContext.getContentResolver(); int otaGuideStatusFlag = Settings.System.getInt(resolver, KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP, VALUE_OTA_GUIDE_DEFAULT); Slog.d(TAG, "ota guide page launch timeout! otaGuideStatusFlag = " + otaGuideStatusFlag); if ((otaGuideStatusFlag == VALUE_OTA_GUIDE_DEFAULT) || (otaGuideStatusFlag == VALUE_OTA_GUIDE_OVERTIME_WAIT)) { dealOtaGuideException(); } break; case MSG_DELAY_TO_CHECK_STATUS_WHEN_OTA_GUIDE_EXIT: Slog.d(TAG, "ota guide exit! "); if (isOtaGuidePageEnable()) { disableOtaGuidePage(); } if (mOtaGuideContentObserver != null) { mContext.getContentResolver().unregisterContentObserver(mOtaGuideContentObserver); } break; default: break ; } } }; } private void initBuildTypes() { mStockType = SystemProperties.get(PROP_STOCK_TYPE); if (isValidStockType(mStockType)) { final String[] typeArr = mStockType.split(DELIMITER); final String region = typeArr[0]; mIsDomesticBuild = "domestic".equalsIgnoreCase(region); final String brand = typeArr[1]; mIsBrandSupported = ("OPPO".equalsIgnoreCase(brand) || "OnePlus".equalsIgnoreCase(brand)); } } private boolean isValidStockType(String type) { final String regex = "^[A-Za-z]+_[A-Za-z]+$"; return (type != null) && (type.matches(regex)); } private void readPrevOsVersionFromXml() { final File file = new File(OS_VERSION_INFO_FILE_PATH); if (!file.exists() || !file.canRead()) { Slog.w(TAG, "Os version info file doesn't exist or can't read"); return; } try (FileInputStream str = new FileInputStream(file)) { final TypedXmlPullParser parser = Xml.resolvePullParser(str); int type; while (((type = parser.next()) != XmlPullParser.START_TAG) && (type != XmlPullParser.END_DOCUMENT)) { ; } if (type != XmlPullParser.START_TAG) { Slog.wtf(TAG, "No start tag found in os version info file"); return; } if (!parser.getName().equals(TAG_OS_VERSION)) { throw new XmlPullParserException("Unexpected start tag in " + file + ": found " + parser.getName() + ", expected 'os_version'"); } int outerDepth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_DOCUMENT) && ((type != XmlPullParser.END_TAG) || (parser.getDepth() > outerDepth))) { if ((type == XmlPullParser.END_TAG) || (type == XmlPullParser.TEXT)) { continue; } String tagName = parser.getName(); if (tagName.equals(TAG_PREV_OS_VERSION)) { mPrevOsVersion = parser.getAttributeValue(null, ATTR_VALUE); } else { Slog.w(TAG, "Unknown element: " + tagName); XmlUtils.skipCurrentTag(parser); } } } catch (IOException | XmlPullParserException e) { Slog.e(TAG, "Error reading os version info file: " + e.toString()); } } private void writeOsVersionToXmlAsync() { Message message = mHandler.obtainMessage(); message.what = MSG_WSN_WRITEOS; mHandler.sendMessage(message); } private void writeOsVersionToXmlSync() { final File file = new File(OS_VERSION_INFO_FILE_PATH); if (file.exists()) { Slog.i(TAG, "Os version info file already exists, replacing..."); file.delete(); } try (FileOutputStream str = new FileOutputStream(file)) { final TypedXmlSerializer serializer = Xml.resolveSerializer(str); serializer.startDocument(null, true); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, TAG_OS_VERSION); // previous os version info serializer.startTag(null, TAG_PREV_OS_VERSION); serializer.attribute(null, ATTR_VALUE, mCurOsVersion); serializer.endTag(null, TAG_PREV_OS_VERSION); serializer.endTag(null, TAG_OS_VERSION); serializer.endDocument(); str.flush(); FileUtils.sync(str); FileUtils.setPermissions(file.toString(), // rw-rw---- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, -1, -1); } catch (IOException e) { Slog.e(TAG, "Error writing os version info file: " + e.toString()); } } private boolean isOsVersionUpgraded() { String prevOsVersion = mPrevOsVersion; String curOsVersion = mCurOsVersion; if (isValidOsVersion(prevOsVersion) && isValidOsVersion(curOsVersion)) { if (prevOsVersion.indexOf(".") == -1) { prevOsVersion = prevOsVersion.concat(".0"); } if (curOsVersion.indexOf(".") == -1) { curOsVersion = curOsVersion.concat(".0"); } try { prevOsVersion = prevOsVersion.substring(1); curOsVersion = curOsVersion.substring(1); String[] prevOsVersionArr = prevOsVersion.split("\\."); String[] curOsVersionArr = curOsVersion.split("\\."); if (((prevOsVersionArr.length == S_OS_VERSION_LENGTH) || (prevOsVersionArr.length == T_OS_VERSION_LENGTH)) && (curOsVersionArr.length == T_OS_VERSION_LENGTH)) { final int prevOsVersionMajor = Integer.parseInt(prevOsVersionArr[0]); final int curOsVersionMajor = Integer.parseInt(curOsVersionArr[0]); if (curOsVersionMajor > prevOsVersionMajor) { return true; } else if (curOsVersionMajor == prevOsVersionMajor) { final int prevOsVersionMinor = Integer.parseInt(prevOsVersionArr[1]); final int curOsVersionMinor = Integer.parseInt(curOsVersionArr[1]); if (curOsVersionMinor > prevOsVersionMinor) { return true; } } } } catch (Exception e) { Slog.e(TAG, "Error parsing os version: " + e.toString()); return false; } } return false; } private boolean isValidOsVersion(String osVersion) { final String regex = "^V\\d+(\\.\\d+)?$"; final String regexForT = "^V\\d+(\\.\\d+\\.\\d+)?$"; return (osVersion != null) && ((osVersion.matches(regex)) || (osVersion.matches(regexForT))); } @Override public void setValues(boolean isFirstBoot, boolean isUpgrade, boolean isBootFromOTA, int curSdkVersion, int lastSdkVersion) { /* * isUpgrade is true when: 1. Fingerprint changes;2. COTA upgrade;3. OTA upgrade (including sau system upgrade);4. opex-apk upgrade; * isBootFromOTA is true: only when OTA upgrade (including sau system upgrade) */ Slog.d(TAG, String.format("firstBoot=%s, upgrade=%s, isBootFromOTA=%s, curSdkVersion=%s, lastSdkVersion=%s", isFirstBoot, isUpgrade, isBootFromOTA, curSdkVersion, lastSdkVersion)); mIsFirstBoot = isFirstBoot; mOtaUpgraded = isUpgrade; mIsBootFromOTA = isBootFromOTA; mPrevSdkVersion = lastSdkVersion; mCurSdkVersion = curSdkVersion; mMajorVersionUpgraded = isUpgrade && lastSdkVersion < curSdkVersion && SUPPORTED_TARGET_SDKS.contains(curSdkVersion); mOsVersionUpgraded = isUpgrade && isOsVersionUpgraded(); mProjectNumber = SystemProperties.get(PROP_RO_SEPARATE_SOFT, ""); } @Override public void enableBootRegIfNeededForDisplayingNewFeatures() { if (!mInitializedSuccessfully) { Slog.w(TAG, "Failed to initialize, skip to enable boot reg"); return; } mIsDeviceTablet = hasOplusFeature(FEATURE_TABLET); mIsScreenFoldable = hasOplusFeature(FEATURE_SCREEN_FOLDABLE); mIsFlipDevice = hasOplusFeature(FEATURE_FOLD_REMAP_DISPLAY_DISABLED); mComponentNameWelcomePage = getBootRegWelcomePageComponentName(); mComponentNameWallPaperGuidPage = getWallPaperGuidComponentName(); Slog.i(TAG, "welcomePage=" + mComponentNameWelcomePage + " mComponentNameWallPaperPage=" + mComponentNameWallPaperGuidPage); final boolean newFeaturesDisplayingEnabled = mIsDomesticBuild && mIsBrandSupported; final boolean forcedToDisplayNewFeatures = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_NEW_FEATURES, false); final boolean newPoliciesDisplayingEnabled = mIsDomesticBuild; final boolean forcedToDisplayNewPolicies = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_NEW_POLICIES, false); final boolean forcedToDisplayBootReg = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_BOOTREG, false); final boolean doesBootRegWelcomePageExist = mComponentNameWelcomePage != null; Slog.d(TAG, "deviceTablet=" + mIsDeviceTablet + ", screenFoldable=" + mIsScreenFoldable + ", flipDevice=" + mIsFlipDevice + ", newFeaturesDisplayingEnabled=" + newFeaturesDisplayingEnabled + ", majorVersionUpgraded=" + mMajorVersionUpgraded + ", forcedToDisplayNewFeatures=" + forcedToDisplayNewFeatures + ", newPoliciesDisplayingEnabled=" + newPoliciesDisplayingEnabled + ", osVersionUpgraded=" + mOsVersionUpgraded + ", forcedToDisplayNewPolicies=" + forcedToDisplayNewPolicies + ", welcomePageExist=" + doesBootRegWelcomePageExist); final boolean shouldDisplayNewFeatures = forcedToDisplayNewFeatures || (newFeaturesDisplayingEnabled && mMajorVersionUpgraded); final boolean shouldDisplayNewPolicies = forcedToDisplayNewPolicies || (newPoliciesDisplayingEnabled && mOsVersionUpgraded); if ((shouldDisplayNewFeatures || shouldDisplayNewPolicies || forcedToDisplayBootReg) && doesBootRegWelcomePageExist) { int oldState = mContext.getPackageManager().getComponentEnabledSetting(mComponentNameWelcomePage); Slog.d(TAG, "Old state of " + mComponentNameWelcomePage + " is " + parseComponentState(oldState)); //When the user disables the boot wizard, the WSN will not be available, so no longer configured try { boolean isEnabled = mContext.getPackageManager().getPackageInfo(PKG_NAME_BOOT_REG, 0).applicationInfo.enabled; if (!isEnabled) { return; } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "get bootreg applicationInfo enabled state fail ", e); } if (shouldDisplayNewFeatures) { // Marked that we should display new features later SystemProperties.set(PROP_MARK_TO_DISPLAY_NEW_FEATURES, String.valueOf(true)); } if (shouldDisplayNewPolicies) { // Marked that we should display new policies later SystemProperties.set(PROP_MARK_TO_DISPLAY_NEW_POLICIES, String.valueOf(true)); } if (!((oldState == COMPONENT_ENABLED_STATE_DEFAULT) || (oldState == COMPONENT_ENABLED_STATE_ENABLED))) { Slog.i(TAG, "Enable boot reg again"); mContext.getPackageManager().setComponentEnabledSetting(mComponentNameWelcomePage, COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */); Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM); } } else { displayOtaGuidePageIfNeed(); } final ContentResolver resolver = mContext.getContentResolver(); ContentObserver co = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { final boolean userSystemSetupComplete = Settings.Secure.getIntForUser(resolver, Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 1; Slog.d(TAG, "userSystemSetupComplete=" + userSystemSetupComplete); if (userSystemSetupComplete) { // Reset props if user system setup completed SystemProperties.set(PROP_FORCE_TO_DISPLAY_NEW_FEATURES, String.valueOf(false)); SystemProperties.set(PROP_MARK_TO_DISPLAY_NEW_FEATURES, String.valueOf(false)); SystemProperties.set(PROP_FORCE_TO_DISPLAY_NEW_POLICIES, String.valueOf(false)); SystemProperties.set(PROP_MARK_TO_DISPLAY_NEW_POLICIES, String.valueOf(false)); SystemProperties.set(PROP_FORCE_TO_DISPLAY_BOOTREG, String.valueOf(false)); if ((shouldDisplayNewFeatures || shouldDisplayNewPolicies) && !mTopIsLauncher) { Message message = mHandler.obtainMessage(); message.what = MSG_WSN_EXCEPTION_NEWFEATURE; mHandler.sendMessageDelayed(message,DELAY_EXCEPTION_NEWFEATURE_SENDING_MSEC); } // Write back new os version. writeOsVersionToXmlAsync(); if (mIsBootFromOTA) { String otaVersion = SystemProperties.get(PROP_OTA_VERSION, ""); SystemProperties.set(PROP_MARK_LAST_UPGRADED_VERSION, otaVersion); } } } }; mContext.getContentResolver().registerContentObserver(Settings.Secure .getUriFor( Settings.Secure.USER_SETUP_COMPLETE), false, co, UserHandle.USER_SYSTEM); co.onChange(true); } private boolean isNeedDisplayOtaGuide() { mIsNeedDisplayWallpaper = isNeedDisplayWallpaper(); mIsNeedDisplaySwitchPage = isNeedDisplaySwitchPage(); return mIsNeedDisplayWallpaper || mIsNeedDisplaySwitchPage; } private boolean isNeedDisplaySwitchPage() { if (!SUPPORTED_OTA_SWITCH_TARGET_SDKS.contains(mCurSdkVersion)) { Slog.w(TAG, "isNeedDisplaySwitchPage: current sdk not support ota switch, no need to go on"); return false; } if (!mIsBootFromOTA) { Slog.w(TAG, "isNeedDisplaySwitchPage: current boot is not boot from ota, just normal boot, no need to go on"); return false; } if (mIsDomesticBuild) { Slog.w(TAG, "isNeedDisplaySwitchPage: current boot is domestic build, no need to go on"); return false; } if (isSauSystemUpdate()) { Slog.w(TAG, "isNeedDisplaySwitchPage: current update is SAU system update, no need to go on"); return false; } if (mOsVersionUpgraded || mMajorVersionUpgraded) { Slog.d(TAG, "isNeedDisplaySwitchPage: is osUpgrade or magorUpgrade, no need to go on"); return false; } if (isUpgradeFailed()) { Slog.d(TAG, "isNeedDisplaySwitchPage: is isUpgradeFailed, no need to go on"); return false; } mOtaGuideBeans = OtaLogicManager.getOtaApplications(mContext); if (mOtaGuideBeans == null || mOtaGuideBeans.isEmpty()) { Slog.d(TAG, "no application need start OtaGuidePage."); return false; } return true; } private boolean isNeedDisplayWallpaper() { if (!SUPPORTED_WALLPAPER_TARGET_SDKS.contains(mCurSdkVersion)) { Slog.w(TAG, "isNeedDisplayWallpaper: current sdk not support wallpager change, no need to go on"); return false; } if (!mIsBootFromOTA) { Slog.w(TAG, "isNeedDisplayWallpaper: current boot is not boot from ota, just normal boot, no need to go on"); return false; } if (mIsDeviceTablet || mIsScreenFoldable) { return false; } if (isMemoryForbid()) { Slog.w(TAG, "isNeedDisplayWallpaper: current memInfo is less than 8G, no need to go on"); return false; } if (mComponentNameWallPaperGuidPage == null) { Slog.w(TAG, "isNeedDisplayWallpaper: mComponentNameWallPaperGuidPage is null, no need to go on"); return false; } if (!mMajorVersionUpgraded && SystemProperties.getBoolean(PROP_FORCE_TO_CHANGE_WALLPAPER, true)) { Slog.w(TAG, "isNeedDisplayWallpaper: current boot is not mMajorVersionUpgraded, no need to go on"); return false; } mPictorialAuto = Settings.System.getInt(mContext.getContentResolver(), "oplus_customize_pictorial_auto_play", 0) == 1; boolean isTheme = (mPictorialAuto || isThirdTheme(0) || checkMarketTheme()); boolean needDisplayWallpaper = !(isTheme || isCarrrierOrChannal() || checkCompanyType()); if (!needDisplayWallpaper) { Slog.w(TAG, "isNeedDisplayWallpaper: no needDisplayWallpaper, no need to go on"); return false; } if (mIsDomesticBuild) { Slog.w(TAG, "isNeedDisplayWallpaper: current boot is domestic build, no need to go on"); return false; } if (isSauSystemUpdate()) { Slog.w(TAG, "isNeedDisplayWallpaper: current update is SAU system update, no need to go on"); return false; } return true; } private void displayOtaGuidePageIfNeed () { if (SystemProperties.getBoolean(PROP_OPLUS_CLOSE_OTA_GUIDE_PAGE, false)) { Slog.w(TAG, "otaGuidePage has been closed!!!, no need to go on"); return; } mComponentOtaGuidePage = getOtaGuideComponentName(); if (mComponentOtaGuidePage == null) { Slog.i(TAG, "the component: " + CLS_NAME_OTA_GUIDE_PAGE + " is not exit, do not need to go on, return"); return; } try { boolean isEnabled = mContext.getPackageManager().getPackageInfo(PKG_NAME_UPGRADE_GUIDE, 0).applicationInfo.enabled; if (!isEnabled) { Slog.w(TAG, "get upgradeguide applicationInfo is not enable, return"); disableOtaGuidePage(); return; } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "get UpgradeGuide applicationInfo enabled state fail name not found,return"); return; } if (isOtaGuidePageEnable()) { Slog.i(TAG, "the component: " + CLS_NAME_OTA_GUIDE_PAGE + " is enable, so disable it first"); disableOtaGuidePage(); } if (!isNeedDisplayOtaGuide()) { return; } enableOtaGuidePage(); } private void enableOtaGuidePage () { Slog.d(TAG, "enableOtaGuidePage now!!!"); final ContentResolver resolver = mContext.getContentResolver(); long id = Binder.clearCallingIdentity(); try { Settings.System.putInt(resolver, KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP, VALUE_OTA_GUIDE_OVERTIME_WAIT); mContext.getPackageManager().setComponentEnabledSetting(mComponentOtaGuidePage, COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */); } catch (IllegalArgumentException e) { Slog.e(TAG, "get IllegalArgumentException, so return"); return; } finally { Binder.restoreCallingIdentity(id); } mHandler.sendEmptyMessageDelayed(MSG_DELAY_TO_CHECK_IF_OTA_GUIDE_ABNORMAL, DELAY_EXCEPTION_OTA_GUIDE_MSEC); mOtaGuideContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { int otaGuideStatusFlag = Settings.System.getInt(resolver, KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP, VALUE_OTA_GUIDE_DEFAULT); if (otaGuideStatusFlag == VALUE_OTA_GUIDE_ENTER) { Slog.i(TAG, "the otaGuide StatusFlag is 1, enter normal"); if (mHandler.hasMessages(MSG_DELAY_TO_CHECK_IF_OTA_GUIDE_ABNORMAL)) { mHandler.removeMessages(MSG_DELAY_TO_CHECK_IF_OTA_GUIDE_ABNORMAL); } } else if (otaGuideStatusFlag == VALUE_OTA_GUIDE_EXIT) { Slog.i(TAG, "the otaGuide StatusFlag is 2, exit normal, so unregister Observer"); mHandler.sendEmptyMessageDelayed(MSG_DELAY_TO_CHECK_STATUS_WHEN_OTA_GUIDE_EXIT, DELAY_CHECK_IF_OTA_GUIDE_NORMAL_EXIT_MSEC); } } }; mContext.getContentResolver().registerContentObserver(Settings.System.getUriFor(KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP), false, mOtaGuideContentObserver, UserHandle.USER_SYSTEM); } private ComponentName getOtaGuideComponentName() { final Intent intent = new Intent(); intent.setClassName(PKG_NAME_UPGRADE_GUIDE, CLS_NAME_OTA_GUIDE_PAGE); final ResolveInfoFlags flags = ResolveInfoFlags.of(PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DISABLED_COMPONENTS); final ResolveInfo ri = mContext.getPackageManager().resolveActivityAsUser(intent, flags, UserHandle.USER_SYSTEM); if ((ri != null) && (ri.activityInfo != null)) { return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); } Slog.w(TAG,"get ota GuidePage activity null "); return null; } private boolean isOtaGuidePageEnable() { int componentState = mContext.getPackageManager().getComponentEnabledSetting(getOtaGuideComponentName()); Slog.i(TAG, "componentState state of " + getOtaGuideComponentName() + " is " + parseComponentState(componentState)); return componentState == COMPONENT_ENABLED_STATE_ENABLED; } private void disableOtaGuidePage() { long id = Binder.clearCallingIdentity(); try { Slog.i(TAG, "start to disable mComponentOtaGuidePage"); mContext.getPackageManager().setComponentEnabledSetting(getOtaGuideComponentName(), COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP /* flags */); } catch (IllegalArgumentException e) { Slog.e(TAG, "setComponentEnabledSetting disable mComponentOtaGuidePage get IllegalArgumentException"); } finally { Binder.restoreCallingIdentity(id); } } private void dealOtaGuideException() { Slog.i(TAG, "dealOtaGuideException, disable ota guide and start home."); if (isOtaGuidePageEnable()) { disableOtaGuidePage(); } if (!mTopIsLauncher) { Slog.i(TAG, "current top is not launcher, so start launcher"); startHome(); } if (mOtaGuideContentObserver != null) { mContext.getContentResolver().unregisterContentObserver(mOtaGuideContentObserver); } Settings.System.putInt(mContext.getContentResolver(), KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP, VALUE_OTA_GUIDE_EXIT); } private boolean isSauSystemUpdate() { OplusSystemUpdateInfo oplusSystemUpdateInfo = OplusFeatureCache.get(IOplusPmsSupportedFunctionManager.DEFAULT).getSystemUpdateInfo(); if ((oplusSystemUpdateInfo != null) && oplusSystemUpdateInfo.getUpdateType() == TYPE_SAU_UPDATE) { return true; } else { return false; } } private boolean isUpgradeFailed() { String currOtaVersion = SystemProperties.get(PROP_OTA_VERSION, ""); String lastUpgradedVersion = SystemProperties.get(PROP_MARK_LAST_UPGRADED_VERSION, ""); if (!TextUtils.isEmpty(currOtaVersion) && !TextUtils.isEmpty(lastUpgradedVersion)) { if (currOtaVersion.equals(lastUpgradedVersion)) { return true; } } return false; } @Override public void putExtraIfNeededForDisplayingNewFeatures(String reason, Intent homeIntent, int userId) { if (!mInitializedSuccessfully) { Slog.w(TAG, "Failed to initialize, skip to put extra into intent"); return; } // Start upgrade service if first boot for new features displaying. final boolean newFeaturesDisplayingEnabled = mIsDomesticBuild && mIsBrandSupported; final boolean forcedToDisplayNewFeatures = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_NEW_FEATURES, false); final boolean shouldDisplayNewFeatures = forcedToDisplayNewFeatures || newFeaturesDisplayingEnabled; final boolean doesBootRegWelcomePageExist = mComponentNameWelcomePage != null; if ("systemReady".equals(reason) && shouldDisplayNewFeatures && mIsFirstBoot && doesBootRegWelcomePageExist) { startUpgradeGuide("first boot"); } final boolean markedToDisplayNewFeatures = SystemProperties.getBoolean(PROP_MARK_TO_DISPLAY_NEW_FEATURES, false); final boolean markedToDisplayNewPolicies = SystemProperties.getBoolean(PROP_MARK_TO_DISPLAY_NEW_POLICIES, false); Slog.d(TAG, "markedToDisplayNewFeatures=" + markedToDisplayNewFeatures + ", markedToDisplayNewPolicies=" + markedToDisplayNewPolicies + ", welcomePageExists=" + doesBootRegWelcomePageExist); if ((markedToDisplayNewFeatures || markedToDisplayNewPolicies) && doesBootRegWelcomePageExist) { Slog.d(TAG, String.format("reason=%s, component=%s, user=%s", reason, homeIntent.getComponent(), userId)); mPictorialAuto = Settings.System.getInt(mContext.getContentResolver(), "oplus_customize_pictorial_auto_play", 0) == 1; mChangeWallPaper = !(isMemoryForbid() || mPictorialAuto || isThirdTheme(userId) || isCarrrierOrChannal() || checkMarketTheme() || checkCompanyType()); if (("systemReady".equals(reason) || "noMoreActivities resumeHomeActivity".equals(reason)) && homeIntent.getComponent() != null && PKG_NAME_BOOT_REG.equals(homeIntent.getComponent().getPackageName()) && (userId == UserHandle.USER_SYSTEM)) { if (markedToDisplayNewFeatures) { Slog.i(TAG, "Put extra sdk version '" + mCurSdkVersion + "' into home intent doesChangeWallPaper :" + mChangeWallPaper); if (mComponentNameWallPaperGuidPage != null && SUPPORTED_WALLPAPER_TARGET_SDKS.contains(mCurSdkVersion) && (!(mIsDeviceTablet || mIsScreenFoldable))) { homeIntent.putExtra(EXTRA_MAJOR_WALLPAPER_CHANGED, mChangeWallPaper); } homeIntent.putExtra(EXTRA_MAJOR_VERSION_UPGRADED, mCurSdkVersion); // Start upgrade service if re-launching boot reg. startUpgradeGuide("relaunching boot reg"); } if (markedToDisplayNewPolicies) { homeIntent.putExtra(EXTRA_OS_VERSION_UPGRADED, mCurOsVersion); } } } else if (homeIntent.getComponent() != null && PKG_NAME_UPGRADE_GUIDE.equals(homeIntent.getComponent().getPackageName()) && (userId == UserHandle.USER_SYSTEM)) { if (mIsNeedDisplayWallpaper) { Slog.i(TAG, "Put extra_major_wallpaper_changed into home intent"); homeIntent.putExtra("extra_major_wallpaper_changed", String.valueOf(mCurSdkVersion)); } if (mIsNeedDisplaySwitchPage) { if ((mOtaGuideBeans != null) && (mOtaGuideBeans.size() != 0)) { Slog.i(TAG, "Put extra_ota_switch_list_from_system " + mOtaGuideBeans.toString() + " into home intent"); homeIntent.putParcelableArrayListExtra(EXTRA_OTA_SWITCH_LIST_FROM_SYSTEM, (ArrayList<? extends Parcelable>) mOtaGuideBeans); OtaLogicManager.saveSwitchRecordList(mOtaGuideBeans); } } } else { if (isOtaGuidePageEnable()) { disableOtaGuidePage(); } } } @Override public void handleProcessDied(ProcessRecord app) { final boolean markedToDisplayNewFeatures = SystemProperties.getBoolean(PROP_MARK_TO_DISPLAY_NEW_FEATURES, false); final boolean markedToDisplayNewPolicies = SystemProperties.getBoolean(PROP_MARK_TO_DISPLAY_NEW_POLICIES, false); String pkgName = app.info.packageName; if (markedToDisplayNewFeatures || markedToDisplayNewPolicies) { if (PKG_NAME_UPGRADE_GUIDE.equalsIgnoreCase(pkgName) && mTopIsWsn) { Slog.i(TAG, "upgradeguide crash so direct go to home"); Message message = mHandler.obtainMessage(); message.what = MSG_WSN_STARTHOME; mHandler.sendMessage(message); } } ContentResolver resolver = mContext.getContentResolver(); int otaGuideStatusFlag = Settings.System.getInt(resolver, KEY_OPLUS_CUSTOMIZE_OTA_GUIDE_STEP, VALUE_OTA_GUIDE_DEFAULT); if (PKG_NAME_UPGRADE_GUIDE.equalsIgnoreCase(pkgName) && PKG_NAME_UPGRADE_GUIDE.equalsIgnoreCase(app.info.processName)) { if ((otaGuideStatusFlag == VALUE_OTA_GUIDE_ENTER) || (otaGuideStatusFlag == VALUE_OTA_GUIDE_OVERTIME_WAIT)) { Slog.i(TAG, "handleProcessDied, the otaGuideStatusFlag is " + otaGuideStatusFlag); dealOtaGuideException(); } } } @Override public void notifyActivityResume(String curPkg) { if (PKG_NAME_UPGRADE_GUIDE.equals(curPkg)) { Slog.i(TAG, "notifyActivityResume : " + curPkg + " mTopIsWsn :" + mTopIsWsn); mTopIsWsn = true ; mHandler.removeMessages(MSG_WSN_EXCEPTION_NEWFEATURE); } else if (PKG_NAME_OPPO_DEFAULT_LAUNCHER.equals(curPkg)) { mTopIsLauncher = true ; } } private void startHome() { Intent homeIntent = new Intent(); homeIntent.setAction(LAUNCHER_ACTION); homeIntent.setPackage(PKG_NAME_OPPO_DEFAULT_LAUNCHER); homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); homeIntent.addCategory(LAUNCHER_CATEGORY); mContext.startActivity(homeIntent); } private String parseComponentState(int state) { switch (state) { case COMPONENT_ENABLED_STATE_DEFAULT: return "default"; case COMPONENT_ENABLED_STATE_ENABLED: return "enabled"; case COMPONENT_ENABLED_STATE_DISABLED: return "disabled"; case COMPONENT_ENABLED_STATE_DISABLED_USER: return "disabled-user"; case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: return "disabled-until-used"; default: return "unknown"; } } private ComponentName getBootRegWelcomePageComponentName() { final Intent intent = new Intent(ACTION_WELCOME_PAGE); intent.setPackage(PKG_NAME_BOOT_REG); final ResolveInfoFlags flags = ResolveInfoFlags.of(PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DISABLED_COMPONENTS); final ResolveInfo ri = mContext.getPackageManager().resolveActivityAsUser(intent, flags, UserHandle.USER_SYSTEM); if ((ri != null) && (ri.activityInfo != null)) { return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); } return null; } private ComponentName getWallPaperGuidComponentName() { try { boolean isEnabled = mContext.getPackageManager().getPackageInfo(PKG_NAME_WALLPAPER, 0).applicationInfo.enabled; if (!isEnabled) { Slog.w(TAG, "get WallPaper applicationInfo is not enable, return"); disableOtaGuidePage(); return null; } } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "get WallPaper applicationInfo enabled state fail name not found,return"); return null; } final Intent intent = new Intent(ACTION_WALLPAPER_GUIDE_PAGE); intent.setPackage(PKG_NAME_WALLPAPER); final ResolveInfoFlags flags = ResolveInfoFlags.of(PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DISABLED_COMPONENTS); final ResolveInfo ri = mContext.getPackageManager().resolveActivityAsUser(intent, flags, UserHandle.USER_SYSTEM); if ((ri != null) && (ri.activityInfo != null)) { return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); } return null; } private boolean hasOplusFeature(@NonNull String featureName) { return OplusFeatureCache.getOrCreate(IOplusFeatureConfigManagerInternal.DEFAULT) .hasFeature(featureName); } // Start upgrade guide to unzip some resources. private void startUpgradeGuide(String reason) { Slog.i(TAG, "Start upgrade guide due to " + reason); Intent upgradeServiceIntent = new Intent(ACTION_UPGRADE_SERVICE); upgradeServiceIntent.putExtra(EXTRA_START_FROM_SYSTEM, 1); upgradeServiceIntent.setPackage(PKG_NAME_UPGRADE_GUIDE); try { OplusBackgroundThread.get().getThreadHandler().post(() -> mContext.startServiceAsUser(upgradeServiceIntent, UserHandle.SYSTEM)); } catch (Exception e) { Slog.e(TAG, "Upgrade service " + upgradeServiceIntent + " failed to start!", e); } } /** * @deprecated * Use action intent filter instead, see {@link #getBootRegWelcomePageComponentName()} */ private boolean doesBootRegWelcomePageExist() { ActivityInfo ai = null; try { ai = mContext.getPackageManager().getActivityInfo(COMPONENT_WELCOME_PAGE, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DISABLED_COMPONENTS); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "doesBootRegWelcomePageExist package not found", e); } return ai != null; } private boolean isThirdTheme(int userId) { String key = "persist.sys.themeflag"; if (userId > 0) { key = key + "." + userId; } long themeFlag = android.os.SystemProperties.getLong(key, 0); if (getInstance().mDebug) { Slog.e(TAG, "themeFlag " + themeFlag); } return ((THIRD_FLAG & themeFlag) != 0) || ((CUSTOM_THEME_FLAG & themeFlag) != 0); } private boolean isCarrrierOrChannal() { String isCarrrier = SystemProperties.get(PROP_CARRRIER); String isChannel = SystemProperties.get(PROP_CHANNEL); if (getInstance().mDebug) { Slog.e(TAG, "isCarrrier " + TextUtils.isEmpty(isCarrrier) + " isChannel :" + TextUtils.isEmpty(isChannel)); } return (!TextUtils.isEmpty(isCarrrier) || !TextUtils.isEmpty(isChannel)) && !mIsDomesticBuild; } private boolean checkMarketTheme() { //kekemarket online resource check String isMarketThem = SystemProperties.get(PROP_THEME_UUID); int type = 0; if (!TextUtils.isEmpty(isMarketThem) && !("-1".equals(isMarketThem))) { mKeKeMarketTheme = true; Slog.e(TAG, "is kekemartket source"); return true; } //kekemarket online aod check String aodjson = Settings.Secure.getString(mContext.getContentResolver(), "Setting_AodStyleInfo"); if (!TextUtils.isEmpty(aodjson)) { Slog.e(TAG, "aodjson aod" + aodjson); JSONObject jsonObject = null; try { jsonObject = new JSONObject(aodjson); type = jsonObject.getInt("type"); } catch (JSONException e) { Slog.e(TAG, "Reason parse to jsonObject failed !" + e); } if (type == 1) { Slog.e(TAG, "is kekemartket aod"); mKeKeMarketTheme = true; return true; } } // kekemarket online live paper check String isMarketLivePaper = Settings.System.getString(mContext.getContentResolver(), "persist.sys.oppo.live_wp_uuid"); if (!TextUtils.isEmpty(isMarketLivePaper) && !NOT_MARKET_LIVE_WALLPAPER.contains(isMarketLivePaper)) { Slog.e(TAG, "is kekemartket livepaper"); mKeKeMarketTheme = true; return true; } return false; } private boolean checkCompanyType() { String isCompanyType = SystemProperties.get(PROP_COMPANY_TYPE, ""); if (TextUtils.isEmpty(isCompanyType)) { if (mDebug) { Slog.e(TAG, "checkCompanyType is null"); } return false; } if ("empty".equals(isCompanyType)) { if (mDebug) { Slog.e(TAG, "checkCompanyType true"); } return false; } return true; } /** * Deprecated for what's new 2.0 requirement. */ private boolean doesBootRegNewFeaturePageExist() { ActivityInfo ai = null; try { ai = mContext.getPackageManager().getActivityInfo(COMPONENT_NEW_FEATURE_PAGE, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "doesBootRegNewFeaturePageExist package not found", e); } return ai != null; } private boolean isMemoryForbid() { int currentMem = getTotalProcMemInfoGB(); Slog.i(TAG, "current Mem is " + currentMem); if (currentMem > EIGHT_GB) { return false; } return true; } private static long getProcMemInfoKB(String label) { long[] values = new long[1]; values[0] = -1; String[] labels = new String[] {label}; try { android.os.Process.readProcLines(PROC_MEMINFO_PATH, labels, values); } catch (IllegalArgumentException e) { Slog.e(TAG, "getProcMemInfoKB IllegalArgumentException"); } return values[0]; } public static int getTotalProcMemInfoKB() { if (sTotalProcMemInfoKB == -1) { sTotalProcMemInfoKB = (int) getProcMemInfoKB(LABEL_MEMTOTAL); } return sTotalProcMemInfoKB; } public static int getTotalProcMemInfoGB() { if (sTotalProcMemInfoGB == -1) { double tempValue = (getTotalProcMemInfoKB() * 1.0) / GB_IN_KB; sTotalProcMemInfoGB = DEF_RAM_SIZE.stream() .filter(i -> (i >= tempValue)) .min(Integer::compareTo) .orElse(DEF_RAM_SIZE.get(DEF_RAM_SIZE.size() - 1)); } return sTotalProcMemInfoGB; } @Override public void dump(PrintWriter pw, String[] args) { final boolean newFeaturesDisplayingEnabled = mIsDomesticBuild && mIsBrandSupported; final boolean forcedToDisplayNewFeatures = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_NEW_FEATURES, false); final boolean newPoliciesDisplayingEnabled = mIsDomesticBuild; final boolean forcedToDisplayNewPolicies = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_NEW_POLICIES, false); final boolean forcedToDisplayBootReg = SystemProperties.getBoolean(PROP_FORCE_TO_DISPLAY_BOOTREG, false); pw.println("DUMP OF NFDM:"); pw.println(" firstBoot=" + mIsFirstBoot); pw.println(" otaUpgraded=" + mOtaUpgraded); pw.println(" mIsBootFromOTA=" + mIsBootFromOTA); pw.println(" stockType=" + mStockType); pw.println(" deviceTablet=" + mIsDeviceTablet); pw.println(" screenFoldable=" + mIsScreenFoldable); pw.println(" flipDevice=" + mIsFlipDevice); pw.println(); pw.println(" newFeatures={"); pw.println(" featureEnabled=" + newFeaturesDisplayingEnabled + " ["); pw.println(" domestic=" + mIsDomesticBuild); pw.println(" brandSupported=" + mIsBrandSupported); pw.println(" ]"); pw.println(" forced=" + forcedToDisplayNewFeatures); pw.println(" majorVerUpgraded=" + mMajorVersionUpgraded + " ["); pw.println(" prevSdkVer=" + mPrevSdkVersion); pw.println(" curSdkVer=" + mCurSdkVersion); pw.println(" ]"); pw.println(" }"); pw.println(); pw.println(" newPolicies={"); pw.println(" featureEnabled=" + newPoliciesDisplayingEnabled + " ["); pw.println(" domestic=" + mIsDomesticBuild); pw.println(" ]"); pw.println(" forced=" + forcedToDisplayNewPolicies); pw.println(" forcedBootReg=" + forcedToDisplayBootReg); pw.println(" osVerUpgraded=" + mOsVersionUpgraded + " ["); pw.println(" prevOsVer=" + mPrevOsVersion); pw.println(" curOsVer=" + mCurOsVersion); pw.println(" ]"); pw.println(" wsn variable={"); pw.println(" topIsWsn=" + mTopIsWsn); pw.println(" mTopIsLauncher=" + mTopIsLauncher); pw.println(" mIsNeedDisplayWallpaper=" + mIsNeedDisplayWallpaper); pw.println(" mIsNeedDisplaySwitchPage=" + mIsNeedDisplaySwitchPage); pw.println(" ]"); pw.println(" wallpaper variable={"); pw.println(" mPictorialAuto=" + mPictorialAuto); pw.println(" mKeKeMarketTheme=" + mKeKeMarketTheme); pw.println(" doChangeWallPaper=" + mChangeWallPaper); pw.println(" ]"); pw.println(" }"); } } 结合方法分析为什么会出现ota压测出现进开机向导的情况,两个版本都是16版本
最新发布
08-23
import socket import subprocess import websocket import time import os import threading import json import pyaudio import requests import hashlib import base64 from audioplayer import AudioPlayer import numpy as np from runner import set_global_var, get_global_var device_status = {} def listen_devices(): try: # 检测设备连接状态 result = subprocess.check_output("adb devices", shell=True).decode() current_devices = set(line.split('\t')[0] for line in result.splitlines()[1:] if line) # 检测新连接设备 for dev in current_devices - set(device_status.keys()): print(f"[设备已连接] {dev}") device_status[dev] = "connected" # 检测断开设备 for dev in set(device_status.keys()) - current_devices: print(f"[设备已断开连接] {dev}") del device_status[dev] time.sleep(1) except Exception as e: print(f"设备监控错误: {e}") def pcm_to_utf8(pcm_data: bytearray) -> str: """将16位PCM音频数据转为UTF-8字符串""" def validate_pcm(data: bytearray) -> bool: """验证PCM数据有效性""" return len(data) % 2 == 0 # 16位PCM需为偶数长度 if not validate_pcm(pcm_data): raise ValueError("无效的PCM数据长度,16位PCM需为偶数长度") try: # 转为16位有符号整数数组(小端序) samples = np.frombuffer(pcm_data, dtype='<i2') # 标准化到0-255范围 normalized = ((samples - samples.min()) * (255 / (samples.max() - samples.min()))).astype(np.uint8) # 转换为UTF-8字符串 return bytes(normalized).decode('utf-8', errors='replace') except Exception as e: raise RuntimeError(f"转换失败: {str(e)}") # 打印前32字节的十六进制表示 def parse_packets(buffer): """解析接收到的数据包""" # 解析数据包 end_marker = b'\n\n' while buffer.find(end_marker) != -1: packet_bytes = buffer[:buffer.find(end_marker) + len(end_marker)] buffer = buffer[buffer.find(end_marker) + len(end_marker):] try: json_bytes = packet_bytes[:-len(end_marker)] json_str = json_bytes.decode('utf-8') packet = json.loads(json_str) # 处理数据包 packet_type = packet.get("type") if packet_type == "recording": audio_data = base64.b64decode(packet.get("data", "")) print('audio_data ', audio_data) return audio_data elif packet_type in ["startRecorder", "stopRecord"]: pass # command_callback(packet_type) else: print(f"未知数据包类型: {packet_type}") except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") except Exception as e: print(f"数据包处理错误: {e}") def start_server(port=35000): adb_path = "adb.exe" os.system(f"adb forward tcp:{port} tcp:30000") with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('localhost', port)) #s.bind(('0.0.0.0', port)) #s.listen(5) print(f"服务器已启动,正在监听端口 {port}...") while True: threading.Thread(target=listen_devices).start() #client_socket, addr = s.accept() #print(f"接收到来自 {addr} 的连接") buffer = bytearray() try: while True: data = s.recv(4096) if data == b'': pass else: print('data', data) #buffer.extend(data) if not data: print("连接断开") break buffer.extend(data) hex_preview = parse_packets(buffer) handle_audio_chunk(hex_preview) print('hex_preview',hex_preview) '''if data==b'': pass else: if len(data) > 0: hex_preview = ' '.join(f'{b:02x}' for b in data[:32]) print(f"前32字节: {hex_preview}...") #handle_audio_chunk(hex_preview) # 调试用:将PCM转为UTF-8 if len(data) < 1024: try: utf8_data = pcm_to_utf8(data) print(f"UTF-8预览: {utf8_data[:30]}...") #handle_audio_chunk(utf8_data) except: pass''' except Exception as e: print(f"接收音频数据异常: {e}") # 全局配置信息 # 播放时是否停止收音 stop_recording_when_playing = True # 打断播放的语音指令 stop_playing_words = ["别说了", "停止", "停下"] # 说话人id voice_speaker_id = 5199 # 语音活动检测-静音时间长度,超过这个时间视为停止说话 vad_silent_time = 1.5 # 语音合成参数 tts_params = {"lan": "zh", "cuid": "test-1234", "ctp": 1, "pdt":993, "spd":5, "pit": 5,"aue": 3} # 语音识别开始指令参数 asr_params = { "type": "START", "data": { "dev_pid": 1912, "dev_key": "com.baidu.open", "format": "pcm", "sample": 16000, "cuid": "my_test_dev", "type": 1, "asr_type": 1, "need_mid": False, "need_session_finish": True } } # 全局状态变量 ws_running = False ws_object = None recorder_running = False sound_play_list = [] current_audio_player = None chat_running = False current_query = '' last_asr_time = 0 def ws_send_start_command(ws): message = json.dumps(asr_params) ws.send_text(message) def ws_send_stop_command(ws): # 发送数据 msg_data = { "type": "FINISH", } message = json.dumps(msg_data) ws.send_text(message) def on_ws_message(ws, message): global current_query, last_asr_time data = json.loads(message) cmd_type = data.get("type") if cmd_type == 'MID_TEXT': mid_text = data.get("result") set_global_var("voicebot.asr.mid_text", mid_text) last_asr_time = time.time() # print("voicebot.asr.mid_text:", mid_text) elif cmd_type == "FIN_TEXT": query = data.get("result") # print("asr result:", query) set_global_var("voicebot.asr.result", query) last_asr_time = time.time() if query and len(query) > 0: current_query += query set_global_var("voicebot.chat.query", current_query) if ws_running == False: ws.close() def on_ws_close(ws, close_status_code, close_msg): print("websocket closed:", close_status_code, close_msg) def on_ws_error(ws, error): print(f"websocket Error: {error}") ws.close() def on_ws_open(ws): print("websocket connection opened:", ws) ws_send_start_command(ws) def check_chat(query:str): # for word in stop_playing_words: # if word in query: # stop_sound_player() # return False # if query in stop_playing_words: # stop_sound_player() # return False if is_playing_or_chatting(): return False return True def stop_sound_player(): global chat_running if current_audio_player: current_audio_player.stop() if len(sound_play_list) > 0: sound_play_list.clear() chat_running = False def run_chat(query:str): global chat_running chat_running = True set_global_var("voicebot.chat.query", query) params = {"query": query} params['username'] = get_global_var("voicebot.username") params['password'] = get_global_var("voicebot.password") response = requests.post("http://127.0.0.1:8010/chat", json=params, stream=True) total_reply = '' buffer = '' for line in response.iter_lines(): if line and chat_running: text = line.decode('utf-8') data = json.loads(text[5:]) content = data.get("content") buffer += content buffer = extract_play_text(buffer) total_reply += content set_global_var("voicebot.chat.reply", total_reply) # print(content, end='', flush=True) chat_running = False buffer = buffer.strip() if len(buffer) > 0: add_play_text(buffer) time.sleep(1) set_global_var("voicebot.chat.query", None) set_global_var("voicebot.chat.reply", None) #提取播放文本 def extract_play_text(total_text:str): separators = ",;。!?:,.!?\n" last_start_pos = 0 min_sentence_length = 4 for i in range(0, len(total_text)): if total_text[i] in separators and i - last_start_pos >= min_sentence_length: text = total_text[last_start_pos: i + 1] last_start_pos = i + 1 add_play_text(text.strip()) return total_text[last_start_pos:] #添加播放文本 def add_play_text(text:str): # print("add play text:", text) if len(text) > 1: sound_play_list.append({"text": text, "mp3_file": None}) # 语音合成 下载声音文件 def download_sound_file(text:str, speaker:int=None): if speaker is None: speaker = voice_speaker_id # print("tts create:", text) mp3_path = "sounds/" + str(speaker) if not os.path.exists(mp3_path): os.mkdir(mp3_path) mp3_file = mp3_path + "/" + hashlib.md5(text.encode('utf-8')).hexdigest() + ".mp3" if os.path.exists(mp3_file): return mp3_file params = tts_params params['per'] = speaker params['text'] = text url = "http://25.83.75.1:8088/Others/tts/text2audio/json" response = requests.post(url, json=params) data = response.json() if data['success'] == False: binary_array = json.loads(data['message']['message']) binary_data = bytes(binary_array) string_data = binary_data.decode('utf-8', errors='replace') data = json.loads(string_data) return "sounds/tts-failed.mp3" else: b64_string = data['result'].get('data') mp3_data = base64.b64decode(b64_string) with open(mp3_file, 'wb') as file: file.write(mp3_data) return mp3_file #开始聊天 def is_playing_or_chatting(): return len(sound_play_list) > 0 #播放下一个声音 def play_next_sound(): global sound_play_list, current_audio_player item = sound_play_list[0] mp3_file = item.get("mp3_file") if mp3_file: player = AudioPlayer(mp3_file) current_audio_player = player try: player.play(block=True) except Exception as e: print("player exception:" + e) current_audio_player = None # print("remained sound:", len(sound_play_list)) if len(sound_play_list) > 0: sound_play_list.pop(0) #运行websocket def run_websocket(): global ws_running, ws_object ws_running = True uri = "ws://25.83.75.1:8088/Others/asr/realtime_asr?sn=voicebot" ws = websocket.WebSocketApp(uri, on_message=on_ws_message, on_close=on_ws_close, on_error=on_ws_error) ws_object = ws ws.on_open = on_ws_open ws.run_forever() ws_running = False # print("websocket end") #开始记录 def start_recorder(chuck_size:int=2560): audio = pyaudio.PyAudio() try: stream = audio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=chuck_size) return audio, stream except: print("打开麦克风失败") return None, None #获得不发音的时间 def get_silent_chunk(duration:float=0.16): sample_rate = 16000 # 采样率 num_samples = int(sample_rate * duration) # 计算样本数 silent_data = np.zeros(num_samples, dtype=np.int16) silent_bytes = silent_data.tobytes() return silent_bytes #处理音频块 def handle_audio_chunk(chunk_data:bytes): # 接受外部是否收音的要求 recording = get_global_var("voicebot.recording") if ws_object and ws_object.sock and ws_object.sock.connected: if recording == False or (stop_recording_when_playing and is_playing_or_chatting()): # print("ignor audio chunk:", sound_play_list, chat_running) ws_object.send_bytes(get_silent_chunk()) else: ws_object.send_bytes(chunk_data) #运行录音机 def run_recorder(audio=None, stream=None, chuck_size=2560): global recorder_running recorder_running = True set_global_var("voicebot.recording", True) while recorder_running: chunk_data = stream.read(chuck_size) print('chunk_data)',chunk_data) handle_audio_chunk(chunk_data) stream.stop_stream() stream.close() audio.terminate() # print("recorder end") #运行检查 def run_check(): global ws_running, recorder_running, current_query set_global_var("voicebot.running", True) while ws_running and recorder_running: time.sleep(1) if get_global_var("voicebot.running") == False: break if len(current_query) > 0 and last_asr_time > 0 and time.time() - last_asr_time > vad_silent_time: t = threading.Thread(target=run_chat, args=(current_query,)) t.start() current_query = '' ws_running = recorder_running = False set_global_var("voicebot.running", False) # print("语音助手已经停止") #运行播放机 def run_player(): while ws_running and recorder_running: time.sleep(0.1) if len(sound_play_list) > 0: play_next_sound() def run_tts(): while ws_running and recorder_running: time.sleep(0.1) for item in sound_play_list: if item.get("mp3_file") is None: item['mp3_file'] = download_sound_file(item['text']) def run(): active_threads = threading.enumerate() # 打印每个活跃线程的信息 for t in active_threads: if t.name == 'voicebot-runner': return "语音助手已经在运行中了" audio, stream = start_recorder() if audio is None or stream is None: return {"error": "语音助手开启失败,无法访问麦克风"} t = threading.Thread(target=run_websocket) t.daemon = True t.start() t=threading.Thread(target=start_server()) t.daemon = True t.start() t = threading.Thread(target=run_check, name='voicebot-runner') t.daemon = True t.start() t = threading.Thread(target=run_tts) t.daemon = True t.start() t = threading.Thread(target=run_player) t.daemon = True t.start() return "执行成功" if __name__ == "__main__": #run() start_server() 把这个TTS的功能融入到第一个脚本里面生成新脚本,并且修改安卓的代码package com.example.demoapplication; import android.Manifest; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.speech.tts.TextToSpeech; import android.util.Base64; import android.util.Log; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { private static final String TAG = "AudioRecorder"; private Button startRecordButton; private Button stopRecordButton; private Button uploadButton; // 音频录制相关 private AudioRecord audioRecord; private static final int SAMPLE_RATE = 44100; // 音频采样率 private static final int BUFFER_SIZE; // 静态代码块用于初始化缓冲区大小 static { int minBufferSize = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); } // 确保缓冲区大小有效 BUFFER_SIZE = Math.max(minBufferSize, 4096); } // 多线程任务调度器 private ScheduledExecutorService scheduler; private AtomicBoolean isRecording = new AtomicBoolean(false); // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 // 线程池服务 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 网络服务器相关 private ServerSocket serverSocket; private volatile boolean isServerRunning = true; // 服务器运行状态 private volatile Socket clientSocket; // 客户端Socket连接 private volatile BufferedWriter socketWriter; // Socket写入流 // 文本转语音(TTS)相关变量 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; // 主线程消息处理器,用于UI更新 private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接成功 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 开始录音 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); break; case 0x13: // 数据发送成功 // 减少Toast频率,避免刷屏 if (Math.random() < 0.1) { // 10%概率显示 Toast.makeText(MainActivity.this, "录音数据已发送", Toast.LENGTH_SHORT).show(); } break; case 0x14: // 停止录音 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); break; case 0x15: // 控制指令 Toast.makeText(MainActivity.this, "收到控制指令:" + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; case 0x16: // 错误消息 Toast.makeText(MainActivity.this, "错误: " + msg.obj.toString(), Toast.LENGTH_LONG).show(); break; case 0x17: // 网络状态 Toast.makeText(MainActivity.this, "网络: " + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }; /** * Activity创建时调用,进行初始化操作。 * @param savedInstanceState 保存的状态数据 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); // 初始化视图组件 setupClickListeners(); // 设置点击事件监听器 checkPermissions(); // 检查权限 startServer(30000); // 启动服务器,端口30000 } /** * 初始化UI视图组件 */ private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); uploadButton = findViewById(R.id.uploadButton); stopRecordButton.setEnabled(false); uploadButton.setEnabled(false); } /** * 设置按钮点击事件监听器 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); stopRecordButton.setOnClickListener(v -> stopRecording()); uploadButton.setOnClickListener(v -> uploadRecording()); } /** * 检查录音权限并请求必要权限 */ private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } /** * 开始录音操作 */ private void startRecording() { // 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } // 检查是否正在录音 if (isRecording.get() || audioRecord != null) { sendErrorMessage("录音已在进行中"); return; } // 检查网络连接 if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("客户端未连接,无法录音"); return; } try { // 初始化 AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE ); // 检查初始化状态 if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord 初始化失败"); } // 开始录音 audioRecord.startRecording(); isRecording.set(true); // 更新按钮状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); uploadButton.setEnabled(false); // 创建定时任务发送音频数据 scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 100, TimeUnit.MILLISECONDS); // 提高发送频率 handler.sendEmptyMessage(0x12); // 发送开始录音的消息 // 发送开始录音控制指令 sendControlPacket("startRecorder"); // 播放TTS提示音 playTts("开始录音"); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } /** * 停止录音操作 */ private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); // 更新按钮状态 stopRecordButton.setEnabled(false); uploadButton.setEnabled(true); handler.sendEmptyMessage(0x14); // 发送停止录音的消息 // 发送停止录音控制指令 sendControlPacket("stopRecor"); // 播放TTS提示音 playTts("停止录音"); } /** * 使用TTS播放指定文本 * @param text 要播放的文本内容 */ private void playTts(String text) { if (isTtsInitialized) { // 使用系统TTS播放 ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); Log.i(TAG, "播放TTS: " + text); } else { Log.w(TAG, "TTS未初始化,无法播放: " + text); } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); scheduler = null; } } /** * 上传音频数据到服务器 */ private void uploadAudioData() { if (!isRecording.get() || clientSocket == null || clientSocket.isClosed() || socketWriter == null) { Log.w(TAG, "无法发送音频数据: 录音未进行或客户端未连接"); return; } byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 创建JSON数据包 JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", Base64.encodeToString(buffer, 0, bytesRead, Base64.NO_WRAP)); // 使用NO_WRAP避免换行符 // 发送数据 synchronized (this) { if (socketWriter != null) { socketWriter.write(json.toString()); socketWriter.write("\n\n"); // 添加双换行作为结束标识 socketWriter.flush(); } } handler.sendEmptyMessage(0x13); // 发送录音数据的消息 } } catch (Exception e) { Log.e(TAG, "发送音频数据失败", e); sendErrorMessage("发送音频数据失败: " + e.getMessage()); } } /** * TTS初始化回调方法 * @param status 初始化状态 */ @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { // 设置默认语言为中文 int result = ttsEngine.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "TTS语言不支持中文"); } else { isTtsInitialized = true; Log.i(TAG, "TTS初始化成功,语言设置为中文"); } } else { Log.e(TAG, "TTS初始化失败"); } } /** * 发送控制指令包 * @param type 控制指令类型 */ private void sendControlPacket(String type) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("无法发送控制指令: 客户端未连接"); return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); packet.put("data", JSONObject.NULL); synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); // 双换行作为结束标识 socketWriter.flush(); } } Log.i(TAG, "控制指令发送成功: " + type); } catch (Exception e) { Log.e(TAG, "发送控制指令失败", e); sendErrorMessage("发送控制指令失败: " + e.getMessage()); } } /** * 发送错误消息 * @param message 错误信息 */ private void sendErrorMessage(String message) { Message msg = handler.obtainMessage(0x16, message); handler.sendMessage(msg); } /** * 发送网络状态消息 * @param message 网络状态信息 */ private void sendNetworkMessage(String message) { Message msg = handler.obtainMessage(0x17, message); handler.sendMessage(msg); } /** * 上传录音文件(当前模式下无实际作用) */ private void uploadRecording() { Toast.makeText(this, "该模式下无需上传文件,已实时发送", Toast.LENGTH_SHORT).show(); } /** * 启动服务器监听 * @param port 监听端口号 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); Log.i(TAG, "服务器启动,监听端口: " + port); sendNetworkMessage("服务器启动"); while (isServerRunning) { try { Socket socket = serverSocket.accept(); clientSocket = socket; // 创建输出流 synchronized (this) { socketWriter = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } handler.sendEmptyMessage(0x11); // 发送客户端连接成功的消息 Log.i(TAG, "客户端已连接: " + socket.getInetAddress()); sendNetworkMessage("客户端已连接"); // 启动双向通信处理 executorService.execute(() -> startCommunication(socket)); } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "接受连接失败", e); sendErrorMessage("接受连接失败: " + e.getMessage()); } } } } catch (IOException e) { Log.e(TAG, "服务器启动失败", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } finally { closeServerSocket(); } }); } /** * 开始与客户端的通信 * @param socket 客户端Socket连接 */ private void startCommunication(Socket socket) { try (java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(socket.getInputStream(), "UTF-8"))) { StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1 && isServerRunning) { char ch = (char) c; packetBuilder.append(ch); // 检测到连续两个换行符,表示一个完整的数据包结束 if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); packetBuilder.setLength(0); // 清空构建器 if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); } catch (JSONException e) { Log.w(TAG, "JSON解析失败: " + packet, e); } } } } } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "通信中断", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "通信中断: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } } finally { closeSocket(socket); } } /** * 处理接收到的数据包 * @param jsonObject 接收到的JSON数据包 */ private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); // 发送消息到主线程进行显示 Message msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); Log.i(TAG, "收到控制指令: " + type); // 根据不同类型执行不同操作 switch (type) { case "start_recording": runOnUiThread(this::startRecording); break; case "stop_recording": runOnUiThread(this::stopRecording); break; case "ping": sendResponse("pong"); break; } } catch (JSONException e) { Log.e(TAG, "处理数据包失败", e); } } /** * 发送响应给客户端 * @param responseType 响应类型 */ private void sendResponse(String responseType) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) return; try { JSONObject response = new JSONObject(); response.put("type", responseType); response.put("data", ""); synchronized (this) { if (socketWriter != null) { socketWriter.write(response.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } Log.i(TAG, "发送响应: " + responseType); } catch (Exception e) { Log.e(TAG, "发送响应失败", e); } } /** * 关闭指定的Socket连接 * @param socket 要关闭的Socket */ private void closeSocket(Socket socket) { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { Log.w(TAG, "关闭Socket失败", e); } // 如果是当前客户端Socket,重置引用 if (socket == clientSocket) { clientSocket = null; synchronized (this) { socketWriter = null; } sendNetworkMessage("客户端断开连接"); } } /** * 关闭服务器Socket */ private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } /** * Activity销毁时调用,释放所有资源 */ @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; // 关闭TTS引擎 if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } // 关闭所有资源 closeServerSocket(); closeSocket(clientSocket); executorService.shutdownNow(); releaseAudioResources(); Log.i(TAG, "应用已销毁"); sendNetworkMessage("服务已停止"); } /** * 权限请求结果回调 * @param requestCode 请求码 * @param permissions 请求的权限数组 * @param grantResults 权限授予结果 */ @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 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "录音权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } } }
07-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值