@Override @ServiceThreadOnly protected int getPreferredAddress() { assertRunOnServiceThread(); return SystemProperties.getInt( Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, Constants.ADDR_UNREGISTERED); }
private int getPhoneTypeFromNetworkType() { // When the system property CURRENT_ACTIVE_PHONE, has not been set, // use the system property for default network type. // This is a fail safe, and can only happen at first boot. int mode = SystemProperties.getInt("ro.telephony.default_network", -1); if (mode == -1) return PHONE_TYPE_NONE; return PhoneFactory.getPhoneType(mode); }
private void registerWifiDisplayAdapterLocked() { if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableWifiDisplay) || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) { mWifiDisplayAdapter = new WifiDisplayAdapter( mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mPersistentDataStore); registerDisplayAdapterLocked(mWifiDisplayAdapter); } }
/** Takes a screenshot of the current display and shows an animation. */ void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { // We need to orient the screenshot correctly (and the Surface api seems to take screenshots // only in the natural orientation of the device :!) mDisplay.getRealMetrics(mDisplayMetrics); float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; int rot = mDisplay.getRotation(); // Allow for abnormal hardware orientation rot = (rot + (android.os.SystemProperties.getInt("ro.sf.hwrotation", 0) / 90)) % 4; float degrees = getDegreesForRotation(rot); boolean requiresRotation = (degrees > 0); if (requiresRotation) { // Get the dimensions of the device in its native orientation mDisplayMatrix.reset(); mDisplayMatrix.preRotate(-degrees); mDisplayMatrix.mapPoints(dims); dims[0] = Math.abs(dims[0]); dims[1] = Math.abs(dims[1]); } mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]); if (requiresRotation) { // Rotate the screenshot to the current orientation Bitmap ss = Bitmap.createBitmap( mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(ss); c.translate(ss.getWidth() / 2, ss.getHeight() / 2); c.rotate(degrees); c.translate(-dims[0] / 2, -dims[1] / 2); c.drawBitmap(mScreenBitmap, 0, 0, null); c.setBitmap(null); mScreenBitmap = ss; } // If we couldn't take the screenshot, notify the user if (mScreenBitmap == null) { notifyScreenshotError(mContext, mNotificationManager); finisher.run(); return; } // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation startAnimation( finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, statusBarVisible, navBarVisible); }
public BluetoothOppL2capListener(BluetoothAdapter adapter, int psm) { mBtOppL2cappsm = psm; mAdapter = adapter; if (D) { Log.v(TAG, "Applying OBEX debug system properties."); int debugPsm = SystemProperties.getInt(DEBUG_L2CAP_SRV_PSM, -1); if (debugPsm >= 0) { Log.v(TAG, "DEBUG: Forcing OBEX L2CAP listener on psm: " + debugPsm); mBtOppL2cappsm = debugPsm; } } }
public static boolean isExtraLowMemDevice() { if (MemorySizeMB < 0) { // value system.ram.total is autodeteced int system/core/init MemorySizeMB = SystemProperties.getInt("system.ram.total", 0); } if (MemorySizeMB == 0) { Log.d("MemoryClass", "property not set , something wrong, force to 512M bytes"); new Exception().printStackTrace(); MemorySizeMB = 512; } Log.d("MemoryClass", "memory is " + MemorySizeMB + " Mbytes"); return MemorySizeMB <= 256; }
private void checkGLESVersion() { if (!mGLESVersionCheckComplete) { mGLESVersion = SystemProperties.getInt( "ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); if (mGLESVersion >= GLES20) { mMultipleGLESContextsAllowed = true; } if (LOG_SURFACE) { Log.w( TAG, "checkGLESVersion mGLESVersion =" + " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed); } mGLESVersionCheckComplete = true; } }
/** Finalize view from inflation. */ @Override protected void onFinishInflate() { super.onFinishInflate(); if (DBG) log("onFinishInflate(this = " + this + ")..."); // Check the Media loopback property int property = SystemProperties.getInt("net.lte.VT_LOOPBACK_ENABLE", 0); mIsMediaLoopback = (property == 1) ? true : false; if (DBG) log("Is Media running in loopback mode: " + mIsMediaLoopback); // Get UI widgets mVideoCallPanel = (ViewGroup) findViewById(R.id.videoCallPanel); mZoomControl = (ZoomControlBar) findViewById(R.id.zoom_control); mFarEndView = (TextureView) findViewById(R.id.video_view); mCameraPreview = (TextureView) findViewById(R.id.camera_view); mCameraPicker = (ImageView) findViewById(R.id.camera_picker); // Set listeners mCameraPreview.setSurfaceTextureListener(this); mFarEndView.setSurfaceTextureListener(this); mCameraPicker.setOnClickListener(this); // Get the camera IDs for front and back cameras mVideoCallManager = VideoCallManager.getInstance(mContext); mBackCameraId = mVideoCallManager.getBackCameraId(); mFrontCameraId = mVideoCallManager.getFrontCameraId(); chooseCamera(true); // Check if camera supports dual cameras mNumberOfCameras = mVideoCallManager.getNumberOfCameras(); if (mNumberOfCameras > 1) { mCameraPicker.setVisibility(View.VISIBLE); } else { mCameraPicker.setVisibility(View.GONE); } // Set media event listener mVideoCallManager.setOnParamReadyListener(new ParamReadyListener()); mVideoCallManager.setCvoEventListener(new CvoListener()); }
// 静态方法 锁定屏幕 public static int lockScreenOrientation(Activity mActivity) { Configuration config = mActivity.getResources().getConfiguration(); int mHardwareRotation = SystemProperties.getInt("ro.sf.hwrotation", 0); Log.v( "", ">>>>>>>>>>>> config orientation : " + config.orientation + " , mHardwareRotation : " + mHardwareRotation); switch (mHardwareRotation) { case 0: mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case 90: mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; // case 180: // mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); // break; // case 270: // mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); // break; default: mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; } /* if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { Log.d(TAG, ">>>>>>>>>> lock orientation to landscape"); mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { Log.d(TAG, ">>>>>>>>>> lock orientation to portrait"); mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); }*/ return mHardwareRotation; }
/** * Return if the current radio is LTE on CDMA. This is a tri-state return value as for a period of * time the mode may be unknown. * * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} or * {@link PhoneConstants#LTE_ON_CDMA_TRUE} * @hide */ public static int getLteOnCdmaModeStatic() { int retVal; int curVal; String productType = ""; curVal = SystemProperties.getInt( TelephonyProperties.PROPERTY_LTE_ON_CDMA_DEVICE, PhoneConstants.LTE_ON_CDMA_UNKNOWN); retVal = curVal; if (retVal == PhoneConstants.LTE_ON_CDMA_UNKNOWN) { Matcher matcher = sProductTypePattern.matcher(sKernelCmdLine); if (matcher.find()) { productType = matcher.group(1); if (sLteOnCdmaProductType.equals(productType)) { retVal = PhoneConstants.LTE_ON_CDMA_TRUE; } else { retVal = PhoneConstants.LTE_ON_CDMA_FALSE; } } else { retVal = PhoneConstants.LTE_ON_CDMA_FALSE; } } Rlog.d( TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal + " product_type='" + productType + "' lteOnCdmaProductType='" + sLteOnCdmaProductType + "'"); return retVal; }
/** Helper class for interacting with the call log. */ class CallLogger { private static final String LOG_TAG = CallLogger.class.getSimpleName(); private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); private PhoneGlobals mApplication; private CallLogAsync mCallLog; public CallLogger(PhoneGlobals application, CallLogAsync callLogAsync) { mApplication = application; mCallLog = callLogAsync; } /** * Logs a call to the call log based on the connection object passed in. * * @param c The connection object for the call being logged. * @param callLogType The type of call log entry. * @param slotId The slot id for the call being logged. */ public void logCall(Connection c, int callLogType, int slotId) { final String number = c.getAddress(); final long date = c.getCreateTime(); final long duration = c.getDurationMillis(); final Phone phone = c.getCall().getPhone(); final CallerInfo ci = getCallerInfoFromConnection(c); // May be null. final String logNumber = getLogNumber(c, ci); if (DBG) { log( "- onDisconnect(): logNumber set to:" + PhoneUtils.toLogSafePhoneNumber(logNumber) + ", number set to: " + PhoneUtils.toLogSafePhoneNumber(number)); } /// M: @{ mPhoneType = phone.getPhoneType(); mIsComing = c.isIncoming(); if (FeatureOption.MTK_VT3G324M_SUPPORT) { mVtCall = c.isVideo() ? 1 : 0; } PhoneLog.d(LOG_TAG, "number=" + number + ", duration=" + duration + ", isVT=" + mVtCall); /// @} /// M: For ALPS00114062 @{ // show every connection's last time of conference call. if (needToShowCallTime(c, duration)) { Toast.makeText( mApplication.getApplicationContext(), formatDuration((int) (duration / 1000)), Toast.LENGTH_SHORT) .show(); } /// @} // TODO: In getLogNumber we use the presentation from // the connection for the CNAP. Should we use the one // below instead? (comes from caller info) // For international calls, 011 needs to be logged as + final int presentation = getPresentation(c, ci); final boolean isOtaspNumber = TelephonyCapabilities.supportsOtasp(phone) && phone.isOtaSpNumber(number); // Don't log OTASP calls. if (!isOtaspNumber) { logCall(ci, logNumber, presentation, callLogType, date, duration, slotId); } } /** Came as logCall(Connection,int) but calculates the call type from the connection object. */ public void logCall(Connection c, int slotId) { final Connection.DisconnectCause cause = c.getDisconnectCause(); // Set the "type" to be displayed in the call log (see constants in CallLog.Calls) final int callLogType; if (c.isIncoming()) { callLogType = (PhoneUtils.shouldAutoReject(c) && cause == Connection.DisconnectCause.INCOMING_REJECTED) ? Calls.AUTOREJECTED_TYPE : ((cause == Connection.DisconnectCause.INCOMING_MISSED ? Calls.MISSED_TYPE : Calls.INCOMING_TYPE)); } else { callLogType = Calls.OUTGOING_TYPE; } if (VDBG) log("- callLogType: " + callLogType + ", UserData: " + c.getUserData()); logCall(c, callLogType, slotId); } /** Logs a call to the call from the parameters passed in. */ public void logCall( CallerInfo ci, String number, int presentation, int callType, long start, long duration, int slotId) { final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mApplication); // On some devices, to avoid accidental redialing of // emergency numbers, we *never* log emergency calls to // the Call Log. (This behavior is set on a per-product // basis, based on carrier requirements.) final boolean okToLogEmergencyNumber = mApplication.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log); // Don't log emergency numbers if the device doesn't allow it, boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber; if (isOkToLogThisCall) { if (DBG) { log( "sending Calllog entry: " + ci + ", " + PhoneUtils.toLogSafePhoneNumber(number) + "," + presentation + ", " + callType + ", " + start + ", " + duration); } /// M: For GEMINI or Other type call(Ex: sip call/video call) @{ CallLogAsync.AddCallArgs args; if (mPhoneType == PhoneConstants.PHONE_TYPE_CDMA && mIsCdmaCallWaitingReject) { args = getCallArgsForCdmaCallWaitingReject( ci, number, presentation, callType, start, duration, slotId); } else { args = getCallArgs(ci, number, presentation, callType, start, duration, slotId); } /// @} try { mCallLog.addCall(args); } catch (SQLiteDiskIOException e) { // TODO Auto-generated catch block Log.e(LOG_TAG, "Error!! - logCall() Disk Full!"); e.printStackTrace(); } } reset(); } /** * Get the caller info. * * @param conn The phone connection. * @return The CallerInfo associated with the connection. Maybe null. */ public static CallerInfo getCallerInfoFromConnection(Connection conn) { CallerInfo ci = null; Object o = conn.getUserData(); if ((o == null) || (o instanceof CallerInfo)) { ci = (CallerInfo) o; } else if (o instanceof Uri) { ci = CallerInfo.getCallerInfo(PhoneGlobals.getInstance().getApplicationContext(), (Uri) o); } else { ci = ((PhoneUtils.CallerInfoToken) o).currentInfo; } return ci; } /** * Retrieve the phone number from the caller info or the connection. * * <p>For incoming call the number is in the Connection object. For outgoing call we use the * CallerInfo phoneNumber field if present. All the processing should have been done already (CDMA * vs GSM numbers). * * <p>If CallerInfo is missing the phone number, get it from the connection. Apply the Call Name * Presentation (CNAP) transform in the connection on the number. * * @param conn The phone connection. * @param callerInfo The CallerInfo. Maybe null. * @return the phone number. */ private String getLogNumber(Connection conn, CallerInfo callerInfo) { String number = null; if (conn.isIncoming()) { number = conn.getAddress(); } else { // For emergency and voicemail calls, // CallerInfo.phoneNumber does *not* contain a valid phone // number. Instead it contains an I18N'd string such as // "Emergency Number" or "Voice Mail" so we get the number // from the connection. if (null == callerInfo || TextUtils.isEmpty(callerInfo.phoneNumber) || callerInfo.isEmergencyNumber() || callerInfo.isVoiceMailNumber()) { if (conn.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { // In cdma getAddress() is not always equals to getOrigDialString(). number = conn.getOrigDialString(); } else { number = conn.getAddress(); } } else { number = callerInfo.phoneNumber; } } if (null == number) { return null; } else { int presentation = conn.getNumberPresentation(); // Do final CNAP modifications. String newNumber = PhoneUtils.modifyForSpecialCnapCases(mApplication, callerInfo, number, presentation); if (!PhoneNumberUtils.isUriNumber(number)) { number = PhoneNumberUtils.stripSeparators(number); } if (VDBG) log("getLogNumber: " + number); return number; } } /** * Get the presentation from the callerinfo if not null otherwise, get it from the connection. * * @param conn The phone connection. * @param callerInfo The CallerInfo. Maybe null. * @return The presentation to use in the logs. */ public static int getPresentation(Connection conn, CallerInfo callerInfo) { int presentation; if (null == callerInfo) { presentation = conn.getNumberPresentation(); } else { presentation = callerInfo.numberPresentation; if (DBG) log( "- getPresentation(): ignoring connection's presentation: " + conn.getNumberPresentation()); } if (DBG) log("- getPresentation: presentation: " + presentation); return presentation; } private static void log(String msg) { Log.d(LOG_TAG, msg); } // --------------------------------------- MTK --------------------------------------- private static final int CALL_TYPE_VIDEO = 1; private int mVtCall = 0; // add for VT call private int mPhoneType = PhoneConstants.PHONE_TYPE_NONE; private boolean mIsComing = false; private boolean mIsCdmaCallWaitingReject = false; /* * New Feature by Mediatek Begin. * CR ID: ALPS00114062 */ private String formatDuration(long elapsedSeconds) { long minutes = 0; long seconds = 0; if (elapsedSeconds >= 60) { minutes = elapsedSeconds / 60; elapsedSeconds -= minutes * 60; } seconds = elapsedSeconds; return (mApplication.getString(R.string.card_title_call_ended) + "(" + mApplication.getString(R.string.callDurationFormat, minutes, seconds) + ")"); } /** for CDMA when Timeout or Ignore Waiting Call */ public void setCdmaCallWaitingReject(boolean isCdmaCallWaitingReject) { mIsCdmaCallWaitingReject = isCdmaCallWaitingReject; } /** * Get the call arguments when CallWaiting reject for CDMA. * * @param ci The caller information for the call being logged. * @param number The number for the call being logged. * @param presentation The presentation to use in the logs. * @param callType The type of call log entry. * @param start The start time of call being logged. * @param duration The duration for call being logged. * @param slotId The slot id for the call being logged. * @return CallLogAsync.AddCallArgs related args for the call being logged. */ private CallLogAsync.AddCallArgs getCallArgsForCdmaCallWaitingReject( CallerInfo ci, String number, int presentation, int callType, long start, long duration, int slotId) { if (DBG) { log("getCallArgsForCdmaCallWaitingReject"); } CallLogAsync.AddCallArgs cdmaArgs; if (GeminiUtils.isGeminiSupport()) { if (DBG) { log("getCallArgsForCdmaCallWaitingReject, support gemini."); } int cdmaSimId = 0; if (ITelephonyWrapper.hasIccCard(slotId)) { SimInfoRecord si = SimInfoManager.getSimInfoBySlot(PhoneGlobals.getInstance(), slotId); if (si != null) { cdmaSimId = (int) si.mSimInfoId; } } cdmaArgs = new CallLogAsync.AddCallArgs( mApplication, ci, number, presentation, callType, start, duration, cdmaSimId); } else { cdmaArgs = new CallLogAsync.AddCallArgs( mApplication, ci, number, presentation, callType, start, duration); } return cdmaArgs; } /** * Get the call arguments when disconnected. * * @param ci The caller information for the call being logged. * @param number The number for the call being logged. * @param presentation The presentation to use in the logs. * @param callType The type of call log entry. * @param start The start time of call being logged. * @param duration The duration of call being logged. * @param slotId The slot id for the call being logged. * @return CallLogAsync.AddCallArgs related args for the call being logged. */ private CallLogAsync.AddCallArgs getCallArgs( CallerInfo ci, String number, int presentation, int callType, long start, long duration, int slotId) { CallLogAsync.AddCallArgs args; int simIdEx = CallNotifier.CALL_TYPE_NONE; // Get the phone type for sip boolean isSipCall = false; if (mPhoneType == PhoneConstants.PHONE_TYPE_SIP) { isSipCall = true; } if (!GeminiUtils.isGeminiSupport() || isSipCall) { // Single Card if (isSipCall) { simIdEx = CallNotifier.CALL_TYPE_SIP; } else { simIdEx = CallNotifier.CALL_TYPE_NONE; if (ITelephonyWrapper.hasIccCard(PhoneConstants.GEMINI_SIM_1)) { SimInfoRecord info = SIMInfoWrapper.getDefault().getSimInfoBySlot(0); if (info != null) { simIdEx = (int) info.mSimInfoId; } else { // Give an default simId, in most case, this is invalid simIdEx = 1; } } if (DBG) { log("for single card, simIdEx = " + simIdEx); } } } else { // dual SIM // Geminni Enhancement: change call log to sim id; SimInfoRecord si; if (ITelephonyWrapper.hasIccCard(slotId)) { si = SimInfoManager.getSimInfoBySlot(PhoneGlobals.getInstance(), slotId); if (si != null) { simIdEx = (int) si.mSimInfoId; } } if (DBG) { log("for dual SIM, simIdEx = " + simIdEx); } } if (FeatureOption.MTK_VT3G324M_SUPPORT) { args = new CallLogAsync.AddCallArgs( mApplication, ci, number, presentation, callType, start, duration, simIdEx, mVtCall); } else { args = new CallLogAsync.AddCallArgs( mApplication, ci, number, presentation, callType, start, duration, simIdEx); } addCallHistoryAsync(number, start, duration, isSipCall, slotId); return args; } /** * Add call number info to call history database for international dialing feature !!!! need to * check okToLogThisCall is suitable for international dialing feature * * @param number The number for the call being logged. * @param start The start time of the call being logged. * @param duration The duration of the call being logged. * @param isSipCall whether sip call * @param slotId The slot id for the call being logged. */ private void addCallHistoryAsync( String number, long start, long duration, boolean isSipCall, int slotId) { final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mApplication); if (!isEmergencyNumber && !mIsComing && mVtCall != CALL_TYPE_VIDEO && !isSipCall && duration >= CallNotifier.CALL_DURATION_THRESHOLD_FOR_CALL_HISTORY && slotId > -1) { String countryISO = CallOptionUtils.getCurrentCountryISO(PhoneGlobals.getInstance()); try { new CallHistoryAsync() .addCall( new CallHistoryAsync.AddCallArgs( PhoneGlobals.getInstance(), number, countryISO, start, slotId, GeminiUtils.isGeminiSupport())); } catch (SQLiteDiskIOException e) { // TODO Auto-generated catch block Log.e(LOG_TAG, "Error!! - onDisconnect() Disk Full!"); e.printStackTrace(); } } } /** clear these flags */ public void reset() { mVtCall = 0; mPhoneType = PhoneConstants.PHONE_TYPE_NONE; mIsComing = false; mIsCdmaCallWaitingReject = false; } /** * to query whether need to show call time, if all connections in a conference call is hangup in * the same time, we show the lastest connection's call time * * @return true if need show call time */ private boolean needToShowCallTime(Connection c, long duration) { if (0 != duration / 1000) { if (!c.getCall().isMultiparty()) { log("needToShowCallTime(), not conference call, show call time..."); return true; } else { long minDuration = duration; List<Connection> connections = c.getCall().getConnections(); for (Connection conn : connections) { if (conn.getState() == Call.State.ACTIVE) { log("needToShowCallTime(), still have active connection!"); return false; } if (conn.getDurationMillis() < minDuration) { // get the latest connection in the conference call minDuration = conn.getDurationMillis(); } } if (duration == minDuration) { log( "needToShowCallTime(), current is the lastest connection in Conference call, show call time..."); return true; } } } return false; } }
/** * Performs a number of miscellaneous, non-system-critical actions after the system has finished * booting. */ public class BootReceiver extends BroadcastReceiver { private static final String TAG = "BootReceiver"; // Maximum size of a logged event (files get truncated if they're longer). // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. private static final int LOG_SIZE = SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; private static final File TOMBSTONE_DIR = new File("/data/tombstones"); // The pre-froyo package and class of the system updater, which // ran in the system process. We need to remove its packages here // in order to clean up after a pre-froyo-to-froyo update. private static final String OLD_UPDATER_PACKAGE = "com.google.android.systemupdater"; private static final String OLD_UPDATER_CLASS = "com.google.android.systemupdater.SystemUpdateReceiver"; // Keep a reference to the observer so the finalizer doesn't disable it. private static FileObserver sTombstoneObserver = null; @Override public void onReceive(final Context context, Intent intent) { boolean fromQuickBoot = intent.getBooleanExtra("from_quickboot", false); if (fromQuickBoot) return; // Log boot events in the background to avoid blocking the main thread with I/O new Thread() { @Override public void run() { try { logBootEvents(context); } catch (Exception e) { Slog.e(TAG, "Can't log boot events", e); } try { boolean onlyCore = false; try { onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) .isOnlyCoreApps(); } catch (RemoteException e) { } if (!onlyCore) { removeOldUpdatePackages(context); } } catch (Exception e) { Slog.e(TAG, "Can't remove old update packages", e); } } }.start(); } private void removeOldUpdatePackages(Context context) { Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } private void logBootEvents(Context ctx) throws IOException { final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE); final String headers = new StringBuilder(512) .append("Build: ") .append(Build.FINGERPRINT) .append("\n") .append("Hardware: ") .append(Build.BOARD) .append("\n") .append("Revision: ") .append(SystemProperties.get("ro.revision", "")) .append("\n") .append("Bootloader: ") .append(Build.BOOTLOADER) .append("\n") .append("Radio: ") .append(Build.RADIO) .append("\n") .append("Kernel: ") .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) .append("\n") .toString(); final String bootReason = SystemProperties.get("ro.boot.bootreason", null); String recovery = RecoverySystem.handleAftermath(); if (recovery != null && db != null) { db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); } String lastKmsgFooter = ""; if (bootReason != null) { lastKmsgFooter = new StringBuilder(512) .append("\n") .append("Boot info:\n") .append("Last boot reason: ") .append(bootReason) .append("\n") .toString(); } if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { if ("encrypted".equals(SystemProperties.get("ro.crypto.state")) && "trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))) { // Encrypted, first boot to get PIN/pattern/password so data is tmpfs // Don't set ro.runtime.firstboot so that we will do this again // when data is properly mounted } else { String now = Long.toString(System.currentTimeMillis()); SystemProperties.set("ro.runtime.firstboot", now); } if (db != null) db.addText("SYSTEM_BOOT", headers); // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) addFileWithFootersToDropBox( db, prefs, headers, lastKmsgFooter, "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG"); addFileWithFootersToDropBox( db, prefs, headers, lastKmsgFooter, "/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG"); addFileToDropBox(db, prefs, headers, "/cache/recovery/log", -LOG_SIZE, "SYSTEM_RECOVERY_LOG"); addFileToDropBox( db, prefs, headers, "/cache/recovery/last_kmsg", -LOG_SIZE, "SYSTEM_RECOVERY_KMSG"); addFileToDropBox( db, prefs, headers, "/data/dontpanic/apanic_console", -LOG_SIZE, "APANIC_CONSOLE"); addFileToDropBox( db, prefs, headers, "/data/dontpanic/apanic_threads", -LOG_SIZE, "APANIC_THREADS"); addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT"); addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK"); } else { if (db != null) db.addText("SYSTEM_RESTART", headers); } // Scan existing tombstones (in case any new ones appeared) File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { if (tombstoneFiles[i].isFile()) { addFileToDropBox( db, prefs, headers, tombstoneFiles[i].getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE"); } } // Start watching for new tombstone files; will record them as they occur. // This gets registered with the singleton file observer thread. sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { @Override public void onEvent(int event, String path) { try { File file = new File(TOMBSTONE_DIR, path); if (file.isFile()) { addFileToDropBox(db, prefs, headers, file.getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE"); } } catch (IOException e) { Slog.e(TAG, "Can't log tombstone", e); } } }; sTombstoneObserver.startWatching(); } private static void addFileToDropBox( DropBoxManager db, SharedPreferences prefs, String headers, String filename, int maxSize, String tag) throws IOException { addFileWithFootersToDropBox(db, prefs, headers, "", filename, maxSize, tag); } private static void addFileWithFootersToDropBox( DropBoxManager db, SharedPreferences prefs, String headers, String footers, String filename, int maxSize, String tag) throws IOException { if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled File file = new File(filename); if (file.isDirectory()) return; // Skip subdirectories (likely vendor-specific) long fileTime = file.lastModified(); if (fileTime <= 0) return; // File does not exist if (prefs != null) { long lastTime = prefs.getLong(filename, 0); if (lastTime == fileTime) return; // Already logged this particular file // TODO: move all these SharedPreferences Editor commits // outside this function to the end of logBootEvents prefs.edit().putLong(filename, fileTime).apply(); } Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + footers); } private static void addAuditErrorsToDropBox( DropBoxManager db, SharedPreferences prefs, String headers, int maxSize, String tag) throws IOException { if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled Slog.i(TAG, "Copying audit failures to DropBox"); File file = new File("/proc/last_kmsg"); long fileTime = file.lastModified(); if (fileTime <= 0) { file = new File("/sys/fs/pstore/console-ramoops"); fileTime = file.lastModified(); } if (fileTime <= 0) return; // File does not exist if (prefs != null) { long lastTime = prefs.getLong(tag, 0); if (lastTime == fileTime) return; // Already logged this particular file // TODO: move all these SharedPreferences Editor commits // outside this function to the end of logBootEvents prefs.edit().putLong(tag, fileTime).apply(); } String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); StringBuilder sb = new StringBuilder(); for (String line : log.split("\n")) { if (line.contains("audit")) { sb.append(line + "\n"); } } Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); db.addText(tag, headers + sb.toString()); } private static void addFsckErrorsToDropBox( DropBoxManager db, SharedPreferences prefs, String headers, int maxSize, String tag) throws IOException { boolean upload_needed = false; if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled Slog.i(TAG, "Checking for fsck errors"); File file = new File("/dev/fscklogs/log"); long fileTime = file.lastModified(); if (fileTime <= 0) return; // File does not exist String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); StringBuilder sb = new StringBuilder(); for (String line : log.split("\n")) { if (line.contains("FILE SYSTEM WAS MODIFIED")) { upload_needed = true; break; } } if (upload_needed) { addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag); } // Remove the file so we don't re-upload if the runtime restarts. file.delete(); } }
/** Top-level Application class for the Phone app. */ public class PhoneApp extends Application { /* package */ static final String LOG_TAG = "PhoneApp"; /** * Phone app-wide debug level: 0 - no debug logging 1 - normal debug logging if ro.debuggable is * set (which is true in "eng" and "userdebug" builds but not "user" builds) 2 - ultra-verbose * debug logging * * <p>Most individual classes in the phone app have a local DBG constant, typically set to * (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1) or else * (PhoneApp.DBG_LEVEL >= 2) depending on the desired verbosity. */ /* package */ static final int DBG_LEVEL = 1; private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2); // Message codes; see mHandler below. private static final int EVENT_SIM_ABSENT = 1; private static final int EVENT_SIM_LOCKED = 2; private static final int EVENT_SIM_NETWORK_LOCKED = 3; private static final int EVENT_WIRED_HEADSET_PLUG = 7; private static final int EVENT_SIM_STATE_CHANGED = 8; private static final int EVENT_UPDATE_INCALL_NOTIFICATION = 9; private static final int EVENT_DATA_ROAMING_DISCONNECTED = 10; private static final int EVENT_DATA_ROAMING_OK = 11; private static final int EVENT_UNSOL_CDMA_INFO_RECORD = 12; // The MMI codes are also used by the InCallScreen. public static final int MMI_INITIATE = 51; public static final int MMI_COMPLETE = 52; public static final int MMI_CANCEL = 53; // Don't use message codes larger than 99 here; those are reserved for // the individual Activities of the Phone UI. /** * Allowable values for the poke lock code (timeout between a user activity and the going to * sleep), please refer to {@link com.android.server.PowerManagerService} for additional * reference. SHORT uses the short delay for the timeout (SHORT_KEYLIGHT_DELAY, 6 sec) MEDIUM uses * the medium delay for the timeout (MEDIUM_KEYLIGHT_DELAY, 15 sec) DEFAULT is the system-wide * default delay for the timeout (1 min) */ public enum ScreenTimeoutDuration { SHORT, MEDIUM, DEFAULT } /** * Allowable values for the wake lock code. SLEEP means the device can be put to sleep. PARTIAL * means wake the processor, but we display can be kept off. FULL means wake both the processor * and the display. */ public enum WakeState { SLEEP, PARTIAL, FULL } private static PhoneApp sMe; // A few important fields we expose to the rest of the package // directly (rather than thru set/get methods) for efficiency. Phone phone; CallNotifier notifier; Ringer ringer; BluetoothHandsfree mBtHandsfree; PhoneInterfaceManager phoneMgr; int mBluetoothHeadsetState = BluetoothHeadset.STATE_ERROR; int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_ERROR; boolean mShowBluetoothIndication = false; // Internal PhoneApp Call state tracker CdmaPhoneCallState cdmaPhoneCallState; // The InCallScreen instance (or null if the InCallScreen hasn't been // created yet.) private InCallScreen mInCallScreen; // The currently-active PUK entry activity and progress dialog. // Normally, these are the Emergency Dialer and the subsequent // progress dialog. null if there is are no such objects in // the foreground. private Activity mPUKEntryActivity; private ProgressDialog mPUKEntryProgressDialog; private boolean mIsSimPinEnabled; private String mCachedSimPin; // True if a wired headset is currently plugged in, based on the state // from the latest Intent.ACTION_HEADSET_PLUG broadcast we received in // mReceiver.onReceive(). private boolean mIsHeadsetPlugged; private WakeState mWakeState = WakeState.SLEEP; private ScreenTimeoutDuration mScreenTimeoutDuration = ScreenTimeoutDuration.DEFAULT; private boolean mIgnoreTouchUserActivity = false; private IBinder mPokeLockToken = new Binder(); private IPowerManager mPowerManagerService; private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mPartialWakeLock; private KeyguardManager mKeyguardManager; private KeyguardManager.KeyguardLock mKeyguardLock; // Broadcast receiver for various intent broadcasts (see onCreate()) private final BroadcastReceiver mReceiver = new PhoneAppBroadcastReceiver(); // Broadcast receiver purely for ACTION_MEDIA_BUTTON broadcasts private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver(); /** boolean indicating restoring mute state on InCallScreen.onResume() */ private boolean mShouldRestoreMuteOnInCallResume; // add by cytown private static final String ACTION_VIBRATE_45 = "com.android.phone.PhoneApp.ACTION_VIBRATE_45"; private CallFeaturesSetting mSettings; private static PendingIntent mVibrateIntent; private static Vibrator mVibrator = null; private static AlarmManager mAM; public void startVib45(long callDurationMsec) { if (VDBG) Log.i(LOG_TAG, "vibrate start @" + callDurationMsec); stopVib45(); long nextalarm = SystemClock.elapsedRealtime() + ((callDurationMsec > 45000) ? 45000 + 60000 - callDurationMsec : 45000 - callDurationMsec); java.lang.System.out.println("am at: " + nextalarm); mAM.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextalarm, mVibrateIntent); } public void stopVib45() { if (VDBG) Log.i(LOG_TAG, "vibrate stop @" + SystemClock.elapsedRealtime()); mAM.cancel(mVibrateIntent); } private final class TriVibRunnable implements Runnable { private int v1, p1, v2; TriVibRunnable(int a, int b, int c) { v1 = a; p1 = b; v2 = c; } public void run() { if (DBG) Log.i(LOG_TAG, "vibrate " + v1 + ":" + p1 + ":" + v2); if (v1 > 0) mVibrator.vibrate(v1); if (p1 > 0) SystemClock.sleep(p1); if (v2 > 0) mVibrator.vibrate(v2); } } public void vibrate(int v1, int p1, int v2) { new Handler().post(new TriVibRunnable(v1, p1, v2)); } /** * Set the restore mute state flag. Used when we are setting the mute state OUTSIDE of user * interaction {@link PhoneUtils#startNewCall(Phone)} */ /*package*/ void setRestoreMuteOnInCallResume(boolean mode) { mShouldRestoreMuteOnInCallResume = mode; } /** * Get the restore mute state flag. This is used by the InCallScreen {@link * InCallScreen#onResume()} to figure out if we need to restore the mute state for the current * active call. */ /*package*/ boolean getRestoreMuteOnInCallResume() { return mShouldRestoreMuteOnInCallResume; } Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_SIM_LOCKED: // mIsSimPinEnabled = true; // // if (Config.LOGV) Log.v(LOG_TAG, "show sim unlock panel"); // SimPinUnlockPanel pinUnlockPanel = new SimPinUnlockPanel( // PhoneApp.getInstance()); // pinUnlockPanel.show(); break; case EVENT_SIM_ABSENT: // Don't need this now that the lock screen handles this case // if (Config.LOGV) Log.v(LOG_TAG, "show sim missing panel"); // SimMissingPanel missingPanel = new SimMissingPanel( // PhoneApp.getInstance()); // missingPanel.show(); break; case EVENT_SIM_NETWORK_LOCKED: if (getResources().getBoolean(R.bool.ignore_sim_network_locked_events)) { // Some products don't have the concept of a "SIM network lock" Log.i( LOG_TAG, "Ignoring EVENT_SIM_NETWORK_LOCKED event; " + "not showing 'SIM network unlock' PIN entry screen"); } else { // Normal case: show the "SIM network unlock" PIN entry screen. // The user won't be able to do anything else until // they enter a valid SIM network PIN. Log.i(LOG_TAG, "show sim depersonal panel"); IccNetworkDepersonalizationPanel ndpPanel = new IccNetworkDepersonalizationPanel(PhoneApp.getInstance()); ndpPanel.show(); } break; case EVENT_UPDATE_INCALL_NOTIFICATION: // Tell the NotificationMgr to update the "ongoing // call" icon in the status bar, if necessary. // Currently, this is triggered by a bluetooth headset // state change (since the status bar icon needs to // turn blue when bluetooth is active.) NotificationMgr.getDefault().updateInCallNotification(); break; case EVENT_DATA_ROAMING_DISCONNECTED: NotificationMgr.getDefault().showDataDisconnectedRoaming(); break; case EVENT_DATA_ROAMING_OK: NotificationMgr.getDefault().hideDataDisconnectedRoaming(); break; case MMI_COMPLETE: onMMIComplete((AsyncResult) msg.obj); break; case MMI_CANCEL: PhoneUtils.cancelMmiCode(phone); break; case EVENT_WIRED_HEADSET_PLUG: // Since the presence of a wired headset or bluetooth affects the // speakerphone, update the "speaker" state. We ONLY want to do // this on the wired headset connect / disconnect events for now // though, so we're only triggering on EVENT_WIRED_HEADSET_PLUG. if (!isHeadsetPlugged() && (mBtHandsfree == null || !mBtHandsfree.isAudioOn())) { // is the state is "not connected", restore the speaker state. PhoneUtils.restoreSpeakerMode(getApplicationContext()); } NotificationMgr.getDefault().updateSpeakerNotification(); break; case EVENT_SIM_STATE_CHANGED: // Marks the event where the SIM goes into ready state. // Right now, this is only used for the PUK-unlocking // process. if (msg.obj.equals(IccCard.INTENT_VALUE_ICC_READY)) { // when the right event is triggered and there // are UI objects in the foreground, we close // them to display the lock panel. if (mPUKEntryActivity != null) { mPUKEntryActivity.finish(); mPUKEntryActivity = null; } if (mPUKEntryProgressDialog != null) { mPUKEntryProgressDialog.dismiss(); mPUKEntryProgressDialog = null; } } break; case EVENT_UNSOL_CDMA_INFO_RECORD: // TODO: handle message here; break; } } }; public PhoneApp() { sMe = this; } @Override public void onCreate() { if (Config.LOGV) Log.v(LOG_TAG, "onCreate()..."); ContentResolver resolver = getContentResolver(); if (phone == null) { // Initialize the telephony framework PhoneFactory.makeDefaultPhones(this); // Get the default phone phone = PhoneFactory.getDefaultPhone(); NotificationMgr.init(this); phoneMgr = new PhoneInterfaceManager(this, phone); if (getSystemService(Context.BLUETOOTH_SERVICE) != null) { mBtHandsfree = new BluetoothHandsfree(this, phone); startService(new Intent(this, BluetoothHeadsetService.class)); } else { // Device is not bluetooth capable mBtHandsfree = null; } ringer = new Ringer(phone); // before registering for phone state changes PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock( PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, LOG_TAG); // lock used to keep the processor awake, when we don't care for the display. mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, LOG_TAG); mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); mKeyguardLock = mKeyguardManager.newKeyguardLock(LOG_TAG); // get a handle to the service so that we can use it later when we // want to set the poke lock. mPowerManagerService = IPowerManager.Stub.asInterface(ServiceManager.getService("power")); notifier = new CallNotifier(this, phone, ringer, mBtHandsfree); // register for ICC status IccCard sim = phone.getIccCard(); if (sim != null) { if (Config.LOGV) Log.v(LOG_TAG, "register for ICC status"); sim.registerForAbsent(mHandler, EVENT_SIM_ABSENT, null); sim.registerForLocked(mHandler, EVENT_SIM_LOCKED, null); sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null); } // register for MMI/USSD phone.registerForMmiComplete(mHandler, MMI_COMPLETE, null); // register connection tracking to PhoneUtils PhoneUtils.initializeConnectionHandler(phone); // Register for misc other intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); intentFilter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION); intentFilter.addAction(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION); intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); intentFilter.addAction(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(Intent.ACTION_BATTERY_LOW); intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED); intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); intentFilter.addAction(ACTION_VIBRATE_45); registerReceiver(mReceiver, intentFilter); // Use a separate receiver for ACTION_MEDIA_BUTTON broadcasts, // since we need to manually adjust its priority (to make sure // we get these intents *before* the media player.) IntentFilter mediaButtonIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON); // // Make sure we're higher priority than the media player's // MediaButtonIntentReceiver (which currently has the default // priority of zero; see apps/Music/AndroidManifest.xml.) mediaButtonIntentFilter.setPriority(1); // registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter); // set the default values for the preferences in the phone. PreferenceManager.setDefaultValues(this, R.xml.network_setting, false); PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false); // Make sure the audio mode (along with some // audio-mode-related state of our own) is initialized // correctly, given the current state of the phone. switch (phone.getState()) { case IDLE: if (DBG) Log.d(LOG_TAG, "Resetting audio state/mode: IDLE"); PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE); PhoneUtils.setAudioMode(this, AudioManager.MODE_NORMAL); break; case RINGING: if (DBG) Log.d(LOG_TAG, "Resetting audio state/mode: RINGING"); PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_RINGING); PhoneUtils.setAudioMode(this, AudioManager.MODE_RINGTONE); break; case OFFHOOK: if (DBG) Log.d(LOG_TAG, "Resetting audio state/mode: OFFHOOK"); PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK); PhoneUtils.setAudioMode(this, AudioManager.MODE_IN_CALL); break; } } // XXX pre-load the SimProvider so that it's ready resolver.getType(Uri.parse("content://icc/adn")); // start with the default value to set the mute state. mShouldRestoreMuteOnInCallResume = false; // add by cytown mSettings = CallFeaturesSetting.getInstance(PreferenceManager.getDefaultSharedPreferences(this)); if (mVibrator == null) { mVibrator = (Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE); mAM = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); mVibrateIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_VIBRATE_45), 0); } // Register for Cdma Information Records // TODO(Moto): Merge // phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null); if (phone.getPhoneName().equals("CDMA")) { // Create an instance of CdmaPhoneCallState and initialize it to IDLE cdmaPhoneCallState = new CdmaPhoneCallState(); cdmaPhoneCallState.CdmaPhoneCallStateInit(); } } /** Returns the singleton instance of the PhoneApp. */ static PhoneApp getInstance() { return sMe; } CallFeaturesSetting getSettings() { return mSettings; } Ringer getRinger() { return ringer; } BluetoothHandsfree getBluetoothHandsfree() { return mBtHandsfree; } static Intent createCallLogIntent() { Intent intent = new Intent(Intent.ACTION_VIEW, null); intent.setType("vnd.android.cursor.dir/calls"); return intent; } /** * Return an Intent that can be used to bring up the in-call screen. * * <p>This intent can only be used from within the Phone app, since the InCallScreen is not * exported from our AndroidManifest. */ /* package */ static Intent createInCallIntent() { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION); intent.setClassName("com.android.phone", getCallScreenClassName()); return intent; } /** * Variation of createInCallIntent() that also specifies whether the DTMF dialpad should be * initially visible when the InCallScreen comes up. */ /* package */ static Intent createInCallIntent(boolean showDialpad) { Intent intent = createInCallIntent(); intent.putExtra(InCallScreen.SHOW_DIALPAD_EXTRA, showDialpad); return intent; } static String getCallScreenClassName() { return InCallScreen.class.getName(); } /** Starts the InCallScreen Activity. */ void displayCallScreen() { if (VDBG) Log.d(LOG_TAG, "displayCallScreen()..."); startActivity(createInCallIntent()); Profiler.callScreenRequested(); } /** * Helper function to check for one special feature of the CALL key: Normally, when the phone is * idle, CALL takes you to the call log (see the handler for KEYCODE_CALL in * PhoneWindow.onKeyUp().) But if the phone is in use (either off-hook or ringing) we instead * handle the CALL button by taking you to the in-call UI. * * @return true if we intercepted the CALL keypress (i.e. the phone was in use) * @see DialerActivity#onCreate */ boolean handleInCallOrRinging() { if (phone.getState() != Phone.State.IDLE) { // Phone is OFFHOOK or RINGING. if (DBG) Log.v(LOG_TAG, "handleInCallOrRinging: show call screen"); displayCallScreen(); return true; } return false; } boolean isSimPinEnabled() { return mIsSimPinEnabled; } boolean authenticateAgainstCachedSimPin(String pin) { return (mCachedSimPin != null && mCachedSimPin.equals(pin)); } void setCachedSimPin(String pin) { mCachedSimPin = pin; } void setInCallScreenInstance(InCallScreen inCallScreen) { mInCallScreen = inCallScreen; } /** * @return true if the in-call UI is running as the foreground activity. (In other words, from the * perspective of the InCallScreen activity, return true between onResume() and onPause().) * <p>Note this method will return false if the screen is currently off, even if the * InCallScreen *was* in the foreground just before the screen turned off. (This is because * the foreground activity is always "paused" while the screen is off.) */ boolean isShowingCallScreen() { if (mInCallScreen == null) return false; return mInCallScreen.isForegroundActivity(); } /** * Dismisses the in-call UI. * * <p>This also ensures that you won't be able to get back to the in-call UI via the BACK button * (since this call removes the InCallScreen from the activity history.) */ void dismissCallScreen() { if (mInCallScreen != null) { mInCallScreen.finish(); } } /** * Sets the activity responsible for un-PUK-blocking the device so that we may close it when we * receive a positive result. mPUKEntryActivity is also used to indicate to the device that we are * trying to un-PUK-lock the phone. In other words, iff it is NOT null, then we are trying to * unlock and waiting for the SIM to move to READY state. * * @param activity is the activity to close when PUK has finished unlocking. Can be set to null to * indicate the unlock or SIM READYing process is over. */ void setPukEntryActivity(Activity activity) { mPUKEntryActivity = activity; } Activity getPUKEntryActivity() { return mPUKEntryActivity; } /** * Sets the dialog responsible for notifying the user of un-PUK- blocking - SIM READYing progress, * so that we may dismiss it when we receive a positive result. * * @param dialog indicates the progress dialog informing the user of the state of the device. * Dismissed upon completion of READYing process */ void setPukEntryProgressDialog(ProgressDialog dialog) { mPUKEntryProgressDialog = dialog; } ProgressDialog getPUKEntryProgressDialog() { return mPUKEntryProgressDialog; } /** * Disables the keyguard. This is used by the phone app to allow interaction with the Phone UI * when the keyguard would otherwise be active (like receiving an incoming call while the device * is locked.) * * <p>Any call to this method MUST be followed (eventually) by a corresponding reenableKeyguard() * call. */ /* package */ void disableKeyguard() { if (DBG) Log.d(LOG_TAG, "disable keyguard"); // if (DBG) Log.d(LOG_TAG, "disableKeyguard()...", new Throwable("stack dump")); mKeyguardLock.disableKeyguard(); } /** * Re-enables the keyguard after a previous disableKeyguard() call. * * <p>Any call to this method MUST correspond to (i.e. be balanced with) a previous * disableKeyguard() call. */ /* package */ void reenableKeyguard() { if (DBG) Log.d(LOG_TAG, "re-enable keyguard"); // if (DBG) Log.d(LOG_TAG, "reenableKeyguard()...", new Throwable("stack dump")); mKeyguardLock.reenableKeyguard(); } /** * Controls how quickly the screen times out. * * <p>The poke lock controls how long it takes before the screen powers down, and therefore has no * immediate effect when the current WakeState (see {@link PhoneApp#requestWakeState}) is FULL. If * we're in a state where the screen *is* allowed to turn off, though, the poke lock will * determine the timeout interval (long or short). * * @param shortPokeLock tells the device the timeout duration to use before going to sleep {@link * com.android.server.PowerManagerService#SHORT_KEYLIGHT_DELAY}. */ /* package */ void setScreenTimeout(ScreenTimeoutDuration duration) { if (VDBG) Log.d(LOG_TAG, "setScreenTimeout(" + duration + ")..."); // make sure we don't set the poke lock repeatedly so that we // avoid triggering the userActivity calls in // PowerManagerService.setPokeLock(). if (duration == mScreenTimeoutDuration) { return; } mScreenTimeoutDuration = duration; updatePokeLock(); } /** * Update the state of the poke lock held by the phone app, based on the current desired screen * timeout and the current "ignore user activity on touch" flag. */ private void updatePokeLock() { // This is kind of convoluted, but the basic thing to remember is // that the poke lock just sends a message to the screen to tell // it to stay on for a while. // The default is 0, for a long timeout and should be set that way // when we are heading back into a the keyguard / screen off // state, and also when we're trying to keep the screen alive // while ringing. We'll also want to ignore the cheek events // regardless of the timeout duration. // The short timeout is really used whenever we want to give up // the screen lock, such as when we're in call. int pokeLockSetting = LocalPowerManager.POKE_LOCK_IGNORE_CHEEK_EVENTS; switch (mScreenTimeoutDuration) { case SHORT: // Set the poke lock to timeout the display after a short // timeout (5s). This ensures that the screen goes to sleep // as soon as acceptably possible after we the wake lock // has been released. // pokeLockSetting |= LocalPowerManager.POKE_LOCK_SHORT_TIMEOUT; pokeLockSetting |= mSettings.mScreenAwake ? LocalPowerManager.POKE_LOCK_MEDIUM_TIMEOUT : LocalPowerManager.POKE_LOCK_SHORT_TIMEOUT; break; case MEDIUM: // Set the poke lock to timeout the display after a medium // timeout (15s). This ensures that the screen goes to sleep // as soon as acceptably possible after we the wake lock // has been released. pokeLockSetting |= LocalPowerManager.POKE_LOCK_MEDIUM_TIMEOUT; break; case DEFAULT: default: // set the poke lock to timeout the display after a long // delay by default. // TODO: it may be nice to be able to disable cheek presses // for long poke locks (emergency dialer, for instance). break; } if (mIgnoreTouchUserActivity) { pokeLockSetting |= LocalPowerManager.POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS; } // Send the request try { mPowerManagerService.setPokeLock(pokeLockSetting, mPokeLockToken, LOG_TAG); } catch (RemoteException e) { Log.w(LOG_TAG, "mPowerManagerService.setPokeLock() failed: " + e); } } /** * Controls whether or not the screen is allowed to sleep. * * <p>Once sleep is allowed (WakeState is SLEEP), it will rely on the settings for the poke lock * to determine when to timeout and let the device sleep {@link PhoneApp#setScreenTimeout}. * * @param ws tells the device to how to wake. */ /* package */ void requestWakeState(WakeState ws) { if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")..."); if (mWakeState != ws) { switch (ws) { case PARTIAL: // acquire the processor wake lock, and release the FULL // lock if it is being held. mPartialWakeLock.acquire(); if (mWakeLock.isHeld()) { mWakeLock.release(); } break; case FULL: // acquire the full wake lock, and release the PARTIAL // lock if it is being held. mWakeLock.acquire(); if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; case SLEEP: default: // release both the PARTIAL and FULL locks. if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; } mWakeState = ws; } } /** * If we are not currently keeping the screen on, then poke the power manager to wake up the * screen for the user activity timeout duration. */ /* package */ void wakeUpScreen() { if (mWakeState == WakeState.SLEEP) { if (DBG) Log.d(LOG_TAG, "pulse screen lock"); try { mPowerManagerService.userActivityWithForce(SystemClock.uptimeMillis(), false, true); } catch (RemoteException ex) { // Ignore -- the system process is dead. } } } /** * Sets the wake state and screen timeout based on the current state of the phone, and the current * state of the in-call UI. * * <p>This method is a "UI Policy" wrapper around {@link PhoneApp#requestWakeState} and {@link * PhoneApp#setScreenTimeout}. * * <p>It's safe to call this method regardless of the state of the Phone (e.g. whether or not it's * idle), and regardless of the state of the Phone UI (e.g. whether or not the InCallScreen is * active.) */ /* package */ void updateWakeState() { Phone.State state = phone.getState(); // True if the in-call UI is the foreground activity. // (Note this will be false if the screen is currently off, // since in that case *no* activity is in the foreground.) boolean isShowingCallScreen = isShowingCallScreen(); // True if the InCallScreen's DTMF dialer is currently opened. // (Note this does NOT imply whether or not the InCallScreen // itself is visible.) boolean isDialerOpened = (mInCallScreen != null) && mInCallScreen.isDialerOpened(); // True if the speakerphone is in use. (If so, we *always* use // the default timeout. Since the user is obviously not holding // the phone up to his/her face, we don't need to worry about // false touches, and thus don't need to turn the screen off so // aggressively.) // Note that we need to make a fresh call to this method any // time the speaker state changes. (That happens in // PhoneUtils.turnOnSpeaker().) boolean isSpeakerInUse = (state == Phone.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this); // TODO (bug 1440854): The screen timeout *might* also need to // depend on the bluetooth state, but this isn't as clear-cut as // the speaker state (since while using BT it's common for the // user to put the phone straight into a pocket, in which case the // timeout should probably still be short.) if (DBG) Log.d( LOG_TAG, "updateWakeState: callscreen " + isShowingCallScreen + ", dialer " + isDialerOpened + ", speaker " + isSpeakerInUse + "..."); // // (1) Set the screen timeout. // // Note that the "screen timeout" value we determine here is // meaningless if the screen is forced on (see (2) below.) // if (!isShowingCallScreen || isSpeakerInUse) { // Use the system-wide default timeout. setScreenTimeout(ScreenTimeoutDuration.DEFAULT); } else { // We're on the in-call screen, and *not* using the speakerphone. if (isDialerOpened) { // The DTMF dialpad is up. This case is special because // the in-call UI has its own "touch lock" mechanism to // disable the dialpad after a very short amount of idle // time (to avoid false touches from the user's face while // in-call.) // // In this case the *physical* screen just uses the // system-wide default timeout. setScreenTimeout(ScreenTimeoutDuration.DEFAULT); } else { // We're on the in-call screen, and not using the DTMF dialpad. // There's actually no touchable UI onscreen at all in // this state. Also, the user is (most likely) not // looking at the screen at all, since they're probably // holding the phone up to their face. Here we use a // special screen timeout value specific to the in-call // screen, purely to save battery life. // setScreenTimeout(ScreenTimeoutDuration.MEDIUM); setScreenTimeout( mSettings.mScreenAwake ? ScreenTimeoutDuration.DEFAULT : ScreenTimeoutDuration.MEDIUM); } } // // (2) Decide whether to force the screen on or not. // // Force the screen to be on if the phone is ringing, or if we're // displaying the "Call ended" UI for a connection in the // "disconnected" state. // boolean isRinging = (state == Phone.State.RINGING); boolean showingDisconnectedConnection = PhoneUtils.hasDisconnectedConnections(phone) && isShowingCallScreen; boolean keepScreenOn = isRinging || showingDisconnectedConnection; if (DBG) Log.d( LOG_TAG, "updateWakeState: keepScreenOn = " + keepScreenOn + " (isRinging " + isRinging + ", showingDisc " + showingDisconnectedConnection + ")"); // keepScreenOn == true means we'll hold a full wake lock: requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP); } /** * Wrapper around the PowerManagerService.preventScreenOn() API. This allows the in-call UI to * prevent the screen from turning on even if a subsequent call to updateWakeState() causes us to * acquire a full wake lock. */ /* package */ void preventScreenOn(boolean prevent) { if (VDBG) Log.d(LOG_TAG, "- preventScreenOn(" + prevent + ")..."); try { mPowerManagerService.preventScreenOn(prevent); } catch (RemoteException e) { Log.w(LOG_TAG, "mPowerManagerService.preventScreenOn() failed: " + e); } } /** * Sets or clears the flag that tells the PowerManager that touch (and cheek) events should NOT be * considered "user activity". * * <p>Since the in-call UI is totally insensitive to touch in most states, we set this flag * whenever the InCallScreen is in the foreground. (Otherwise, repeated unintentional touches * could prevent the device from going to sleep.) * * <p>There *are* some some touch events that really do count as user activity, though. For those, * we need to manually poke the PowerManager's userActivity method; see pokeUserActivity(). */ /* package */ void setIgnoreTouchUserActivity(boolean ignore) { if (VDBG) Log.d(LOG_TAG, "setIgnoreTouchUserActivity(" + ignore + ")..."); mIgnoreTouchUserActivity = ignore; updatePokeLock(); } /** * Manually pokes the PowerManager's userActivity method. Since we hold the * POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS poke lock while the InCallScreen is active, we need to * do this for touch events that really do count as user activity (like DTMF key presses, or * unlocking the "touch lock" overlay.) */ /* package */ void pokeUserActivity() { if (VDBG) Log.d(LOG_TAG, "pokeUserActivity()..."); try { mPowerManagerService.userActivity(SystemClock.uptimeMillis(), false); } catch (RemoteException e) { Log.w(LOG_TAG, "mPowerManagerService.userActivity() failed: " + e); } } KeyguardManager getKeyguardManager() { return mKeyguardManager; } private void onMMIComplete(AsyncResult r) { if (VDBG) Log.d(LOG_TAG, "onMMIComplete()..."); MmiCode mmiCode = (MmiCode) r.result; PhoneUtils.displayMMIComplete(phone, getInstance(), mmiCode, null, null); } private void initForNewRadioTechnology() { if (DBG) Log.d(LOG_TAG, "initForNewRadioTechnology..."); ringer.updateRingerContextAfterRadioTechnologyChange(this.phone); notifier.updateCallNotifierRegistrationsAfterRadioTechnologyChange(); if (mBtHandsfree != null) { mBtHandsfree.updateBtHandsfreeAfterRadioTechnologyChange(); } if (mInCallScreen != null) { mInCallScreen.updateAfterRadioTechnologyChange(); } // Update registration for ICC status after radio technology change IccCard sim = phone.getIccCard(); if (sim != null) { if (DBG) Log.d(LOG_TAG, "Update registration for ICC status..."); // Register all events new to the new active phone sim.registerForAbsent(mHandler, EVENT_SIM_ABSENT, null); sim.registerForLocked(mHandler, EVENT_SIM_LOCKED, null); sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null); } if (phone.getPhoneName().equals("CDMA")) { // Create an instance of CdmaPhoneCallState and initialize it to IDLE cdmaPhoneCallState = new CdmaPhoneCallState(); cdmaPhoneCallState.CdmaPhoneCallStateInit(); } } /** Send ECBM Exit Request */ void sendEcbmExitRequest() { mHandler.sendEmptyMessage(EVENT_UPDATE_INCALL_NOTIFICATION); } /** * @return true if a wired headset is currently plugged in. * @see Intent.ACTION_HEADSET_PLUG (which we listen for in mReceiver.onReceive()) */ boolean isHeadsetPlugged() { return mIsHeadsetPlugged; } /** * @return true if the onscreen UI should currently be showing the special "bluetooth is active" * indication in a couple of places (in which UI elements turn blue and/or show the bluetooth * logo.) * <p>This depends on the BluetoothHeadset state *and* the current telephony state; see * shouldShowBluetoothIndication(). * @see CallCard * @see NotificationMgr.updateInCallNotification */ /* package */ boolean showBluetoothIndication() { return mShowBluetoothIndication; } /** * Recomputes the mShowBluetoothIndication flag based on the current bluetooth state and current * telephony state. * * <p>This needs to be called any time the bluetooth headset state or the telephony state changes. * * @param forceUiUpdate if true, force the UI elements that care about this flag to update * themselves. */ /* package */ void updateBluetoothIndication(boolean forceUiUpdate) { mShowBluetoothIndication = shouldShowBluetoothIndication(mBluetoothHeadsetState, mBluetoothHeadsetAudioState, phone); if (forceUiUpdate) { // Post Handler messages to the various components that might // need to be refreshed based on the new state. if (isShowingCallScreen()) mInCallScreen.updateBluetoothIndication(); mHandler.sendEmptyMessage(EVENT_UPDATE_INCALL_NOTIFICATION); } } /** * UI policy helper function for the couple of places in the UI that have some way of indicating * that "bluetooth is in use." * * @return true if the onscreen UI should indicate that "bluetooth is in use", based on the * specified bluetooth headset state, and the current state of the phone. * @see showBluetoothIndication() */ private static boolean shouldShowBluetoothIndication( int bluetoothState, int bluetoothAudioState, Phone phone) { // We want the UI to indicate that "bluetooth is in use" in two // slightly different cases: // // (a) The obvious case: if a bluetooth headset is currently in // use for an ongoing call. // // (b) The not-so-obvious case: if an incoming call is ringing, // and we expect that audio *will* be routed to a bluetooth // headset once the call is answered. switch (phone.getState()) { case OFFHOOK: // This covers normal active calls, and also the case if // the foreground call is DIALING or ALERTING. In this // case, bluetooth is considered "active" if a headset // is connected *and* audio is being routed to it. return ((bluetoothState == BluetoothHeadset.STATE_CONNECTED) && (bluetoothAudioState == BluetoothHeadset.AUDIO_STATE_CONNECTED)); case RINGING: // If an incoming call is ringing, we're *not* yet routing // audio to the headset (since there's no in-call audio // yet!) In this case, if a bluetooth headset is // connected at all, we assume that it'll become active // once the user answers the phone. return (bluetoothState == BluetoothHeadset.STATE_CONNECTED); default: // Presumably IDLE return false; } } /** Receiver for misc intent broadcasts the Phone app cares about. */ private class PhoneAppBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { boolean enabled = System.getInt(getContentResolver(), System.AIRPLANE_MODE_ON, 0) == 0; phone.setRadioPower(enabled); } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) { mBluetoothHeadsetState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, BluetoothHeadset.STATE_ERROR); if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_STATE_CHANGED_ACTION"); if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetState); updateBluetoothIndication(true); // Also update any visible UI if necessary } else if (action.equals(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION)) { mBluetoothHeadsetAudioState = intent.getIntExtra(BluetoothIntent.HEADSET_AUDIO_STATE, BluetoothHeadset.STATE_ERROR); if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION"); if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetAudioState); updateBluetoothIndication(true); // Also update any visible UI if necessary } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"); if (VDBG) Log.d(LOG_TAG, "- state: " + intent.getStringExtra(Phone.STATE_KEY)); if (VDBG) Log.d(LOG_TAG, "- reason: " + intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY)); // The "data disconnected due to roaming" notification is // visible if you've lost data connectivity because you're // roaming and you have the "data roaming" feature turned off. boolean disconnectedDueToRoaming = false; if ("DISCONNECTED".equals(intent.getStringExtra(Phone.STATE_KEY))) { String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); if (Phone.REASON_ROAMING_ON.equals(reason)) { // We just lost our data connection, and the reason // is that we started roaming. This implies that // the user has data roaming turned off. disconnectedDueToRoaming = true; } } mHandler.sendEmptyMessage( disconnectedDueToRoaming ? EVENT_DATA_ROAMING_DISCONNECTED : EVENT_DATA_ROAMING_OK); } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_HEADSET_PLUG"); if (VDBG) Log.d(LOG_TAG, " state: " + intent.getIntExtra("state", 0)); if (VDBG) Log.d(LOG_TAG, " name: " + intent.getStringExtra("name")); mIsHeadsetPlugged = (intent.getIntExtra("state", 0) == 1); mHandler.sendMessage(mHandler.obtainMessage(EVENT_WIRED_HEADSET_PLUG, 0)); } else if (action.equals(Intent.ACTION_BATTERY_LOW)) { if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_BATTERY_LOW"); notifier.sendBatteryLow(); // Play a warning tone if in-call } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) && (mPUKEntryActivity != null)) { // if an attempt to un-PUK-lock the device was made, while we're // receiving this state change notification, notify the handler. // NOTE: This is ONLY triggered if an attempt to un-PUK-lock has // been attempted. mHandler.sendMessage( mHandler.obtainMessage( EVENT_SIM_STATE_CHANGED, intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE))); } else if (action.equals(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED)) { String newPhone = intent.getStringExtra(Phone.PHONE_NAME_KEY); Log.d(LOG_TAG, "Radio technology switched. Now " + newPhone + " is active."); initForNewRadioTechnology(); } else if (action.equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) { handleServiceStateChanged(intent); } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) { Log.d(LOG_TAG, "Emergency Callback Mode arrived in PhoneApp."); // Send Intend to start ECBM application Intent EcbmAlarm = new Intent(Intent.ACTION_MAIN, null); EcbmAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); EcbmAlarm.setClassName("com.android.phone", EmergencyCallbackMode.class.getName()); startActivity(EcbmAlarm); // Vibrate 45 sec receiver add by cytown } else if (action.equals(ACTION_VIBRATE_45)) { if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_VIBRATE_45"); mAM.set( AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60000, mVibrateIntent); if (DBG) Log.i(LOG_TAG, "vibrate on 45 sec"); mVibrator.vibrate(70); SystemClock.sleep(70); mVibrator.cancel(); if (VDBG) Log.d(LOG_TAG, "mReceiver: force vib cancel"); // vibrate(70, 70, -1); } } } /** * Broadcast receiver for the ACTION_MEDIA_BUTTON broadcast intent. * * <p>This functionality isn't lumped in with the other intents in PhoneAppBroadcastReceiver * because we instantiate this as a totally separate BroadcastReceiver instance, since we need to * manually adjust its IntentFilter's priority (to make sure we get these intents *before* the * media player.) */ private class MediaButtonBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver.onReceive()... event = " + event); if ((event != null) && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) && (event.getAction() == KeyEvent.ACTION_DOWN)) { if (event.getRepeatCount() == 0) { // Mute ONLY on the initial keypress. if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver: HEADSETHOOK down!"); boolean consumed = PhoneUtils.handleHeadsetHook(phone); if (VDBG) Log.d(LOG_TAG, "==> handleHeadsetHook(): consumed = " + consumed); if (consumed) { abortBroadcast(); } } else if (phone.getState() != Phone.State.IDLE) { // As for any DOWN events other than the initial press, we consume // (and ignore) those too if the phone is in use. (Otherwise the // music player will handle them, which would be confusing.) abortBroadcast(); } } } } private void handleServiceStateChanged(Intent intent) { /** * This used to handle updating EriTextWidgetProvider this routine and and listening for * ACTION_SERVICE_STATE_CHANGED intents could be removed. But leaving just in case it might be * needed in the near future. */ // If service just returned, start sending out the queued messages ServiceState ss = ServiceState.newFromBundle(intent.getExtras()); boolean hasService = true; boolean isCdma = false; String eriText = ""; if (ss != null) { int state = ss.getState(); switch (state) { case ServiceState.STATE_OUT_OF_SERVICE: case ServiceState.STATE_POWER_OFF: hasService = false; break; } } else { hasService = false; } } }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load preferences from xml. addPreferencesFromResource(FRAGMENT_RES[mXmlResId]); PreferenceScreen screen = getPreferenceScreen(); if (!FeatureOption.MTK_DT_SUPPORT) { removePreference(screen, "dualtalk_network_info"); removePreference(screen, "dualtalk_bandmode"); } // Duplicate with Network Selecting, remove them removePreference(screen, "digital_standard"); if (ModemCategory.getModemType() == ModemCategory.MODEM_TD) { removePreference(screen, "rat_mode"); } if (!FeatureOption.MTK_DT_SUPPORT) { removePreference(screen, "dualtalk_network_select"); } else { removePreference(screen, "network_select"); } // if // (NfcAdapter.getDefaultAdapter(getActivity().getApplicationContext()) // == null) { // removePreference(screen, "nfc"); // } // it's ok int versionCode = Settings.Global.getInt(getActivity().getContentResolver(), "nfc_controller_code", -1); if (FRAGMENT_RES[mXmlResId] == R.xml.connectivity) { switch (versionCode) { case MTK_NFC_CHIP_TYPE_MSR3110: Xlog.i(TAG, "MSR3110 nfc chip, call nfc"); removePreference(screen, "hqanfc"); break; case MTK_NFC_CHIP_TYPE_MT6605: Xlog.i(TAG, "MT6605 nfc chip, call hqanfc"); removePreference(screen, "nfc"); break; default: Xlog.i(TAG, "no nfc chip support"); removePreference(screen, "hqanfc"); removePreference(screen, "nfc"); break; } } if (!FeatureOption.MTK_NFC_SUPPORT) { removePreference(screen, "nfc_dta"); } /*if (!FeatureOption.MTK_LOG2SERVER_APP) { removePreference(screen, "log2server"); }*/ if (FeatureOption.EVDO_DT_SUPPORT || !FeatureOption.MTK_DT_SUPPORT || SystemProperties.getInt("ril.external.md", 0) == 0) { removePreference(screen, "ext_md_logger"); } if (!FeatureOption.MTK_SMSREG_APP) { removePreference(screen, "device_manager"); } if (FeatureOption.MTK_BSP_PACKAGE) { removePreference(screen, "auto_answer"); } if (ChipSupport.isFeatureSupported(ChipSupport.MTK_FM_SUPPORT)) { if (!ChipSupport.isFeatureSupported(ChipSupport.MTK_FM_TX_SUPPORT)) { removePreference(screen, "fm_transmitter"); } } else { removePreference(screen, "fm_receiver"); removePreference(screen, "fm_transmitter"); } // AGPS is not ready if MTK_AGPS_APP isn't defined if (!ChipSupport.isFeatureSupported(ChipSupport.MTK_AGPS_APP) || !ChipSupport.isFeatureSupported(ChipSupport.MTK_GPS_SUPPORT)) { removePreference(screen, "location_basedservice"); } if (!ChipSupport.isFeatureSupported(ChipSupport.MTK_GPS_SUPPORT)) { removePreference(screen, "ygps"); removePreference(screen, "cw_test"); } // MATV is not ready if HAVE_MATV_FEATURE isn't defined if (!ChipSupport.isFeatureSupported(ChipSupport.HAVE_MATV_FEATURE)) { removePreference(screen, "matv"); } // BT is not ready if MTK_BT_SUPPORT isn't defined if (!ChipSupport.isFeatureSupported(ChipSupport.MTK_BT_SUPPORT)) { removePreference(screen, "bluetooth"); } // wifi is not ready if MTK_WLAN_SUPPORT isn't defined if (!ChipSupport.isFeatureSupported(ChipSupport.MTK_WLAN_SUPPORT)) { removePreference(screen, "wifi"); } if (!isVoiceCapable() || isWifiOnly()) { removePreference(screen, "auto_answer"); removePreference(screen, "repeat_call_test"); removePreference(screen, "video_telephony"); } if (!FeatureOption.MTK_VT3G324M_SUPPORT) { removePreference(screen, "video_telephony"); } if (isWifiOnly()) { removePreference(screen, "GPRS"); removePreference(screen, "Modem"); removePreference(screen, "NetworkInfo"); removePreference(screen, "Baseband"); removePreference(screen, "SIMMeLock"); removePreference(screen, "BandMode"); removePreference(screen, "RAT Mode"); removePreference(screen, "SWLA"); removePreference(screen, "ModemTest"); } // if it single sim, then the flow is the same as before if (FeatureOption.MTK_GEMINI_SUPPORT) { /** * if it is Gemini, then the flow is : it start a TabActivity, then the TabActivity will start * sim1 or sim2 simLock activity Intent to launch SIM lock TabActivity */ // intent.setComponent(new // ComponentName("com.android.simmelock","com.android.simmelock.TabLockList")); removePreference(screen, "simme_lock1"); } else { // Intent to launch SIM lock settings // intent.setComponent(new // ComponentName("com.android.simmelock","com.android.simmelock.LockList")); removePreference(screen, "simme_lock2"); } Xlog.i(TAG, "ChipSupport.getChip(): " + ChipSupport.getChip()); if (ChipSupport.MTK_6589_SUPPORT > ChipSupport.getChip()) { removePreference(screen, "de_sense"); removePreference(screen, "camera89"); } else { removePreference(screen, "camera"); } if (!FeatureOption.MTK_FD_SUPPORT) { removePreference(screen, "fast_dormancy"); } File innerLoadIndicator = new File(INNER_LOAD_INDICATOR_FILE); if (!innerLoadIndicator.exists()) { removePreference(screen, "system_update"); } if (!ChipSupport.isChipInSet(ChipSupport.CHIP_657X_SERIES_NEW)) { removePreference(screen, "deep_idle"); removePreference(screen, "sleep_mode"); removePreference(screen, "dcm"); removePreference(screen, "pll_cg"); removePreference(screen, "cpu_dvfs"); removePreference(screen, "mcdi_setting"); } if (!FeatureOption.MTK_CDS_EM_SUPPORT) { removePreference(screen, "cds_information"); } Preference pref = (Preference) findPreference("cmas"); if (pref != null && !isActivityAvailable(pref.getIntent())) { removePreference(screen, "cmas"); } if (!FeatureOption.MTK_WORLD_PHONE) { removePreference(screen, "world_phone"); } if (!FeatureOption.EVDO_DT_SUPPORT) { removePreference(screen, "saber"); } String mOptr = SystemProperties.get("ro.operator.optr"); if (!"OP01".equals(mOptr)) { removePreference(screen, "ConfigureCheck2_Send_Test"); removePreference(screen, "ConfigureCheck2_Self_Test"); } if ("OP09".equals(mOptr)) { // For CT removePreference(screen, "network_select"); removePreference(screen, "dualtalk_network_select"); } else { removePreference(screen, "cdma_network_select"); } if (!FeatureOption.MTK_DEVREG_APP) { removePreference(screen, "device_register"); } if (!FeatureOption.MTK_WFD_SUPPORT) { removePreference(screen, "wfd_settings"); } if (!FeatureOption.MTK_LTE_DC_SUPPORT) { removePreference(screen, "lte_config"); } if (!FeatureOption.MTK_LTE_SUPPORT) { removePreference(screen, "lte_network_info"); } removePreference(screen, "lte_network_mode"); if (!CipUtil.isCipSupported()) { removePreference(screen, "cip"); } if (UserHandle.MU_ENABLED && UserManager.supportsMultipleUsers()) { // Remove all items used phone instance removePreference(screen, "bandmode"); removePreference(screen, "te_dev_tool"); removePreference(screen, "cds_information"); removePreference(screen, "cfu"); removePreference(screen, "fast_dormancy"); removePreference(screen, "gprs"); removePreference(screen, "hspa_info"); removePreference(screen, "mobile_data_prefer"); removePreference(screen, "modem_test"); removePreference(screen, "modem_warning"); removePreference(screen, "network_info"); removePreference(screen, "dualtalk_network_info"); removePreference(screen, "network_select"); removePreference(screen, "cdma_network_select"); removePreference(screen, "dualtalk_network_select"); removePreference(screen, "rat_mode"); removePreference(screen, "rf_desense_test"); removePreference(screen, "swla"); removePreference(screen, "simme_lock1"); removePreference(screen, "simme_lock2"); removePreference(screen, "world_phone"); removePreference(screen, "lte_config"); removePreference(screen, "lte_network_mode"); removePreference(screen, "lte_network_info"); removePreference(screen, "lte_tool"); } if (!FeatureOption.MTK_DFO_RESOLUTION_SUPPORT) { removePreference(screen, "dfo"); } }
private final void init() { char[] buffer = new char[1024]; boolean inAccessoryMode = false; mHasUsbService = SystemProperties.getInt("ro.usb.use_custom_service", 0) != 0; // Read initial USB state (device mode) mConfiguration = -1; try { FileReader file = new FileReader(USB_CONNECTED_PATH); int len = file.read(buffer, 0, 1024); file.close(); mConnected = Integer.valueOf((new String(buffer, 0, len)).trim()); file = new FileReader(USB_CONFIGURATION_PATH); len = file.read(buffer, 0, 1024); file.close(); mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim()); } catch (FileNotFoundException e) { Slog.i(TAG, "This kernel does not have USB configuration switch support"); Slog.i(TAG, "Trying legacy USB configuration switch support"); try { FileReader file = new FileReader(USB_LEGACY_PATH); int len = file.read(buffer, 0, 1024); file.close(); mConnected = ("online".equals((new String(buffer, 0, len)).trim()) ? 1 : 0); mLegacy = true; mConfiguration = 0; } catch (FileNotFoundException f) { Slog.i(TAG, "This kernel does not have legacy USB configuration switch support"); } catch (Exception f) { Slog.e(TAG, "", f); } } catch (Exception e) { Slog.e(TAG, "", e); } if (mConfiguration < 0) { // This may happen in the emulator or devices without USB device mode support return; } File compositeDir = new File(USB_COMPOSITE_CLASS_PATH); if (compositeDir.exists()) { inAccessoryMode |= initFunctions(compositeDir, true, buffer); } else { Slog.w(TAG, "This kernel does not have USB composite class support"); compositeDir = new File(USB_LEGACY_FUNCTIONS_PATH); if (compositeDir.exists()) { inAccessoryMode |= initFunctions(compositeDir, false, buffer); } else if (mLegacy) { mDisabledFunctions.add(UsbManager.USB_FUNCTION_MASS_STORAGE); } } // handle the case where an accessory switched the driver to accessory mode // before the framework finished booting if (inAccessoryMode && !mLegacy) { readCurrentAccessoryLocked(); // FIXME - if we booted in accessory mode, then we have no way to figure out // which functions are enabled by default. // For now, assume that MTP or mass storage are the only possibilities if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MTP)) { mDefaultFunctions.add(UsbManager.USB_FUNCTION_MTP); } else if (mDisabledFunctions.contains(UsbManager.USB_FUNCTION_MASS_STORAGE)) { mDefaultFunctions.add(UsbManager.USB_FUNCTION_MASS_STORAGE); } } }
private int getPhoneTypeFromProperty() { int type = SystemProperties.getInt( TelephonyProperties.CURRENT_ACTIVE_PHONE, getPhoneTypeFromNetworkType()); return type; }
public void testReadSysProperty() throws Exception { assertEquals(1, SystemProperties.getInt("sys.boot_completed", 0)); }
@Override public void run() { boolean waitedHalf = false; while (true) { mCompleted = false; mHandler.sendEmptyMessage(MONITOR); synchronized (this) { long timeout = TIME_TO_WAIT; // NOTE: We use uptimeMillis() here because we do not want to increment the time we // wait while asleep. If the device is asleep then the thing that we are waiting // to timeout on is asleep as well and won't have a chance to run, causing a false // positive on when to kill things. long start = SystemClock.uptimeMillis(); while (timeout > 0 && !mForceKillSystem) { try { wait(timeout); // notifyAll() is called when mForceKillSystem is set } catch (InterruptedException e) { Log.wtf(TAG, e); } timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); } if (mCompleted && !mForceKillSystem) { // The monitors have returned. waitedHalf = false; continue; } if (!waitedHalf) { // We've waited half the deadlock-detection interval. Pull a stack // trace and wait another half. ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); ActivityManagerService.dumpStackTraces(true, pids, null, null); waitedHalf = true; continue; } } // If we got here, that means that the system is most likely hung. // First collect stack traces from all threads of the system process. // Then kill this process so that the system will restart. final String name = (mCurrentMonitor != null) ? mCurrentMonitor.getClass().getName() : "null"; EventLog.writeEvent(EventLogTags.WATCHDOG, name); ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); if (mPhonePid > 0) pids.add(mPhonePid); // Pass !waitedHalf so that just in case we somehow wind up here without having // dumped the halfway stacks, we properly re-initialize the trace file. final File stack = ActivityManagerService.dumpStackTraces(!waitedHalf, pids, null, null); // Give some extra time to make sure the stack traces get written. // The system's been hanging for a minute, another second or two won't hurt much. SystemClock.sleep(2000); // Pull our own kernel thread stacks as well if we're configured for that if (RECORD_KERNEL_THREADS) { dumpKernelStackTraces(); } String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); if (tracesPath != null && tracesPath.length() != 0) { File traceRenameFile = new File(tracesPath); String newTracesPath; int lpos = tracesPath.lastIndexOf("."); if (-1 != lpos) newTracesPath = tracesPath.substring(0, lpos) + "_SystemServer_WDT" + tracesPath.substring(lpos); else newTracesPath = tracesPath + "_SystemServer_WDT"; traceRenameFile.renameTo(new File(newTracesPath)); tracesPath = newTracesPath; } final File newFd = new File(tracesPath); // Try to add the error to the dropbox, but assuming that the ActivityManager // itself may be deadlocked. (which has happened, causing this statement to // deadlock and the watchdog as a whole to be ineffective) Thread dropboxThread = new Thread("watchdogWriteToDropbox") { public void run() { mActivity.addErrorToDropBox( "watchdog", null, "system_server", null, null, name, null, newFd, null); } }; dropboxThread.start(); try { dropboxThread.join(2000); // wait up to 2 seconds for it to return. } catch (InterruptedException ignored) { } // Only kill the process if the debugger is not attached. if (!Debug.isDebuggerConnected()) { if (SystemProperties.getInt("sys.watchdog.disabled", 0) == 0) { Process.sendSignal(Process.myPid(), 6); SystemClock.sleep(2000); Process.sendSignal(Process.myPid(), 6); SystemClock.sleep(2000); String procs = SystemProperties.get("sys.watchdog.extraprocnames"); if (procs != null && procs.length() > 0) { Slog.w(TAG, "Processes to create tombstones: [" + procs + "]"); createTombstone(procs); } else { Slog.w(TAG, "No process is given for creating tombstones on framework reboot."); } Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name); Process.killProcess(Process.myPid()); System.exit(10); } else { Slog.w(TAG, "*** WATCHDOG NOT KILLING SYSTEM PROCESS: " + name); // Send the SIGSTOP to the System Process for DEBUG purposes Process.sendSignal(Process.myPid(), 19); break; } } else { Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process"); } waitedHalf = false; } }
/** * Helper class to manage the "Respond via SMS" feature for incoming calls. * * @see InCallScreen.internalRespondViaSms() */ public class RespondViaSmsManager { private static final String TAG = "RespondViaSmsManager"; private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); // Do not check in with VDBG = true, since that may write PII to the system log. private static final boolean VDBG = false; /** * Reference to the InCallScreen activity that owns us. This may be null if we haven't been * initialized yet *or* after the InCallScreen activity has been destroyed. */ private InCallScreen mInCallScreen; /** * The popup showing the list of canned responses. * * <p>This is an AlertDialog containing a ListView showing the possible choices. This may be null * if the InCallScreen hasn't ever called showRespondViaSmsPopup() yet, or if the popup was * visible once but then got dismissed. */ private Dialog mPopup; /** The array of "canned responses"; see loadCannedResponses(). */ private String[] mCannedResponses; /** SharedPreferences file name for our persistent settings. */ private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs"; // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings. // Since (for now at least) the number of messages is fixed at 4, and since // SharedPreferences can't deal with arrays anyway, just store the messages // as 4 separate strings. private static final int NUM_CANNED_RESPONSES = 4; private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1"; private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2"; private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3"; private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4"; private static final String ACTION_SENDTO_NO_CONFIRMATION = "com.android.mms.intent.action.SENDTO_NO_CONFIRMATION"; /** RespondViaSmsManager constructor. */ public RespondViaSmsManager() {} public void setInCallScreenInstance(InCallScreen inCallScreen) { mInCallScreen = inCallScreen; if (mInCallScreen != null) { // Prefetch shared preferences to make the first canned response lookup faster // (and to prevent StrictMode violation) mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); } } /** * Brings up the "Respond via SMS" popup for an incoming call. * * @param ringingCall the current incoming call */ public void showRespondViaSmsPopup(Call ringingCall) { if (DBG) log("showRespondViaSmsPopup()..."); ListView lv = new ListView(mInCallScreen); // Refresh the array of "canned responses". mCannedResponses = loadCannedResponses(); // Build the list: start with the canned responses, but manually add // "Custom message..." as the last choice. int numPopupItems = mCannedResponses.length + 1; String[] popupItems = Arrays.copyOf(mCannedResponses, numPopupItems); popupItems[numPopupItems - 1] = mInCallScreen.getResources().getString(R.string.respond_via_sms_custom_message); ArrayAdapter<String> adapter = new ArrayAdapter<String>( mInCallScreen, android.R.layout.simple_list_item_1, android.R.id.text1, popupItems); lv.setAdapter(adapter); // Create a RespondViaSmsItemClickListener instance to handle item // clicks from the popup. // (Note we create a fresh instance for each incoming call, and // stash away the call's phone number, since we can't necessarily // assume this call will still be ringing when the user finally // chooses a response.) Connection c = ringingCall.getLatestConnection(); if (VDBG) log("- connection: " + c); if (c == null) { // Uh oh -- the "ringingCall" doesn't have any connections any more. // (In other words, it's no longer ringing.) This is rare, but can // happen if the caller hangs up right at the exact moment the user // selects the "Respond via SMS" option. // There's nothing to do here (since the incoming call is gone), // so just bail out. Log.i(TAG, "showRespondViaSmsPopup: null connection; bailing out..."); return; } // TODO: at this point we probably should re-check c.getAddress() // and c.getNumberPresentation() for validity. (i.e. recheck the // same cases in InCallTouchUi.showIncomingCallWidget() where we // should have disallowed the "respond via SMS" feature in the // first place.) String phoneNumber = c.getAddress(); if (VDBG) log("- phoneNumber: " + phoneNumber); lv.setOnItemClickListener(new RespondViaSmsItemClickListener(phoneNumber)); AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen) .setCancelable(true) .setOnCancelListener(new RespondViaSmsCancelListener()) .setView(lv); mPopup = builder.create(); mPopup.show(); } /** * Dismiss the "Respond via SMS" popup if it's visible. * * <p>This is safe to call even if the popup is already dismissed, and even if you never called * showRespondViaSmsPopup() in the first place. */ public void dismissPopup() { if (mPopup != null) { mPopup.dismiss(); // safe even if already dismissed mPopup = null; } } public boolean isShowingPopup() { return mPopup != null && mPopup.isShowing(); } /** OnItemClickListener for the "Respond via SMS" popup. */ public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener { // Phone number to send the SMS to. private String mPhoneNumber; public RespondViaSmsItemClickListener(String phoneNumber) { mPhoneNumber = phoneNumber; } /** Handles the user selecting an item from the popup. */ @Override public void onItemClick( AdapterView<?> parent, // The ListView View view, // The TextView that was clicked int position, long id) { if (DBG) log("RespondViaSmsItemClickListener.onItemClick(" + position + ")..."); String message = (String) parent.getItemAtPosition(position); if (VDBG) log("- message: '" + message + "'"); // The "Custom" choice is a special case. // (For now, it's guaranteed to be the last item.) if (position == (parent.getCount() - 1)) { // Take the user to the standard SMS compose UI. launchSmsCompose(mPhoneNumber); } else { // Send the selected message immediately with no user interaction. sendText(mPhoneNumber, message); // ...and show a brief confirmation to the user (since // otherwise it's hard to be sure that anything actually // happened.) final Resources res = mInCallScreen.getResources(); String formatString = res.getString(R.string.respond_via_sms_confirmation_format); String confirmationMsg = String.format(formatString, mPhoneNumber); Toast.makeText(mInCallScreen, confirmationMsg, Toast.LENGTH_LONG).show(); // TODO: If the device is locked, this toast won't actually ever // be visible! (That's because we're about to dismiss the call // screen, which means that the device will return to the // keyguard. But toasts aren't visible on top of the keyguard.) // Possible fixes: // (1) Is it possible to allow a specific Toast to be visible // on top of the keyguard? // (2) Artifically delay the dismissCallScreen() call by 3 // seconds to allow the toast to be seen? // (3) Don't use a toast at all; instead use a transient state // of the InCallScreen (perhaps via the InCallUiState // progressIndication feature), and have that state be // visible for 3 seconds before calling dismissCallScreen(). } // At this point the user is done dealing with the incoming call, so // there's no reason to keep it around. (It's also confusing for // the "incoming call" icon in the status bar to still be visible.) // So reject the call now. mInCallScreen.hangupRingingCall(); dismissPopup(); final PhoneConstants.State state = PhoneApp.getInstance().mCM.getState(); if (state == PhoneConstants.State.IDLE) { // There's no other phone call to interact. Exit the entire in-call screen. PhoneApp.getInstance().dismissCallScreen(); } else { // The user is still in the middle of other phone calls, so we should keep the // in-call screen. mInCallScreen.requestUpdateScreen(); } } } /** OnCancelListener for the "Respond via SMS" popup. */ public class RespondViaSmsCancelListener implements DialogInterface.OnCancelListener { public RespondViaSmsCancelListener() {} /** * Handles the user canceling the popup, either by touching outside the popup or by pressing * Back. */ @Override public void onCancel(DialogInterface dialog) { if (DBG) log("RespondViaSmsCancelListener.onCancel()..."); dismissPopup(); final PhoneConstants.State state = PhoneApp.getInstance().mCM.getState(); if (state == PhoneConstants.State.IDLE) { // This means the incoming call is already hung up when the user chooses not to // use "Respond via SMS" feature. Let's just exit the whole in-call screen. PhoneApp.getInstance().dismissCallScreen(); } else { // If the user cancels the popup, this presumably means that // they didn't actually mean to bring up the "Respond via SMS" // UI in the first place (and instead want to go back to the // state where they can either answer or reject the call.) // So restart the ringer and bring back the regular incoming // call UI. // This will have no effect if the incoming call isn't still ringing. PhoneApp.getInstance().notifier.restartRinger(); // We hid the GlowPadView widget way back in // InCallTouchUi.onTrigger(), when the user first selected // the "SMS" trigger. // // To bring it back, just force the entire InCallScreen to // update itself based on the current telephony state. // (Assuming the incoming call is still ringing, this will // cause the incoming call widget to reappear.) mInCallScreen.requestUpdateScreen(); } } } /** Sends a text message without any interaction from the user. */ private void sendText(String phoneNumber, String message) { if (VDBG) log("sendText: number " + phoneNumber + ", message '" + message + "'"); mInCallScreen.startService(getInstantTextIntent(phoneNumber, message)); } /** Brings up the standard SMS compose UI. */ private void launchSmsCompose(String phoneNumber) { if (VDBG) log("launchSmsCompose: number " + phoneNumber); Intent intent = getInstantTextIntent(phoneNumber, null); if (VDBG) log("- Launching SMS compose UI: " + intent); mInCallScreen.startService(intent); } /** * @param phoneNumber Must not be null. * @param message Can be null. If message is null, the returned Intent will be configured to * launch the SMS compose UI. If non-null, the returned Intent will cause the specified * message to be sent with no interaction from the user. * @return Service Intent for the instant response. */ private static Intent getInstantTextIntent(String phoneNumber, String message) { Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null); Intent intent = new Intent(ACTION_SENDTO_NO_CONFIRMATION, uri); if (message != null) { intent.putExtra(Intent.EXTRA_TEXT, message); } else { intent.putExtra("exit_on_sent", true); intent.putExtra("showUI", true); } return intent; } /** * Settings activity under "Call settings" to let you manage the canned responses; see * respond_via_sms_settings.xml */ public static class Settings extends PreferenceActivity implements Preference.OnPreferenceChangeListener { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); if (DBG) log("Settings: onCreate()..."); getPreferenceManager().setSharedPreferencesName(SHARED_PREFERENCES_NAME); // This preference screen is ultra-simple; it's just 4 plain // <EditTextPreference>s, one for each of the 4 "canned responses". // // The only nontrivial thing we do here is copy the text value of // each of those EditTextPreferences and use it as the preference's // "title" as well, so that the user will immediately see all 4 // strings when they arrive here. // // Also, listen for change events (since we'll need to update the // title any time the user edits one of the strings.) addPreferencesFromResource(R.xml.respond_via_sms_settings); EditTextPreference pref; pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_1); pref.setTitle(pref.getText()); pref.setOnPreferenceChangeListener(this); pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_2); pref.setTitle(pref.getText()); pref.setOnPreferenceChangeListener(this); pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_3); pref.setTitle(pref.getText()); pref.setOnPreferenceChangeListener(this); pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_4); pref.setTitle(pref.getText()); pref.setOnPreferenceChangeListener(this); ActionBar actionBar = getActionBar(); if (actionBar != null) { // android.R.id.home will be triggered in onOptionsItemSelected() actionBar.setDisplayHomeAsUpEnabled(true); } } // Preference.OnPreferenceChangeListener implementation @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (DBG) log("onPreferenceChange: key = " + preference.getKey()); if (VDBG) log(" preference = '" + preference + "'"); if (VDBG) log(" newValue = '" + newValue + "'"); EditTextPreference pref = (EditTextPreference) preference; // Copy the new text over to the title, just like in onCreate(). // (Watch out: onPreferenceChange() is called *before* the // Preference itself gets updated, so we need to use newValue here // rather than pref.getText().) pref.setTitle((String) newValue); return true; // means it's OK to update the state of the Preference with the new value } @Override public boolean onOptionsItemSelected(MenuItem item) { final int itemId = item.getItemId(); if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled() CallFeaturesSetting.goUpToTopLevelSetting(this); return true; } return super.onOptionsItemSelected(item); } } /** * Read the (customizable) canned responses from SharedPreferences, or from defaults if the user * has never actually brought up the Settings UI. * * <p>This method does disk I/O (reading the SharedPreferences file) so don't call it from the * main thread. * * @see RespondViaSmsManager.Settings */ private String[] loadCannedResponses() { if (DBG) log("loadCannedResponses()..."); SharedPreferences prefs = mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); final Resources res = mInCallScreen.getResources(); String[] responses = new String[NUM_CANNED_RESPONSES]; // Note the default values here must agree with the corresponding // android:defaultValue attributes in respond_via_sms_settings.xml. responses[0] = prefs.getString( KEY_CANNED_RESPONSE_PREF_1, res.getString(R.string.respond_via_sms_canned_response_1)); responses[1] = prefs.getString( KEY_CANNED_RESPONSE_PREF_2, res.getString(R.string.respond_via_sms_canned_response_2)); responses[2] = prefs.getString( KEY_CANNED_RESPONSE_PREF_3, res.getString(R.string.respond_via_sms_canned_response_3)); responses[3] = prefs.getString( KEY_CANNED_RESPONSE_PREF_4, res.getString(R.string.respond_via_sms_canned_response_4)); return responses; } /** * @return true if the "Respond via SMS" feature should be enabled for the specified incoming * call. * <p>The general rule is that we *do* allow "Respond via SMS" except for the few (relatively * rare) cases where we know for sure it won't work, namely: - a bogus or blank incoming * number - a call from a SIP address - a "call presentation" that doesn't allow the number to * be revealed * <p>In all other cases, we allow the user to respond via SMS. * <p>Note that this behavior isn't perfect; for example we have no way to detect whether the * incoming call is from a landline (with most networks at least), so we still enable this * feature even though SMSes to that number will silently fail. */ public static boolean allowRespondViaSmsForCall(Context context, Call ringingCall) { if (DBG) log("allowRespondViaSmsForCall(" + ringingCall + ")..."); // First some basic sanity checks: if (ringingCall == null) { Log.w(TAG, "allowRespondViaSmsForCall: null ringingCall!"); return false; } if (!ringingCall.isRinging()) { // The call is in some state other than INCOMING or WAITING! // (This should almost never happen, but it *could* // conceivably happen if the ringing call got disconnected by // the network just *after* we got it from the CallManager.) Log.w( TAG, "allowRespondViaSmsForCall: ringingCall not ringing! state = " + ringingCall.getState()); return false; } Connection conn = ringingCall.getLatestConnection(); if (conn == null) { // The call doesn't have any connections! (Again, this can // happen if the ringing call disconnects at the exact right // moment, but should almost never happen in practice.) Log.w(TAG, "allowRespondViaSmsForCall: null Connection!"); return false; } // Check the incoming number: final String number = conn.getAddress(); if (DBG) log("- number: '" + number + "'"); if (TextUtils.isEmpty(number)) { Log.w(TAG, "allowRespondViaSmsForCall: no incoming number!"); return false; } if (PhoneNumberUtils.isUriNumber(number)) { // The incoming number is actually a URI (i.e. a SIP address), // not a regular PSTN phone number, and we can't send SMSes to // SIP addresses. // (TODO: That might still be possible eventually, though. Is // there some SIP-specific equivalent to sending a text message?) Log.i(TAG, "allowRespondViaSmsForCall: incoming 'number' is a SIP address."); return false; } // Finally, check the "call presentation": int presentation = conn.getNumberPresentation(); if (DBG) log("- presentation: " + presentation); if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { // PRESENTATION_RESTRICTED means "caller-id blocked". // The user isn't allowed to see the number in the first // place, so obviously we can't let you send an SMS to it. Log.i(TAG, "allowRespondViaSmsForCall: PRESENTATION_RESTRICTED."); return false; } // Allow the feature only when there's a destination for it. if (context.getPackageManager().resolveService(getInstantTextIntent(number, null), 0) == null) { return false; } // TODO: with some carriers (in certain countries) you *can* actually // tell whether a given number is a mobile phone or not. So in that // case we could potentially return false here if the incoming call is // from a land line. // If none of the above special cases apply, it's OK to enable the // "Respond via SMS" feature. return true; } private static void log(String msg) { Log.d(TAG, msg); } }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate"); loadApps(); // do this in onresume? setContentView(R.layout.facebook_start_ui); mContainer = (ViewGroup) findViewById(R.id.container); mContainer.setPersistentDrawingCache(ViewGroup.PERSISTENT_ANIMATION_CACHE); // this.getWindow().setBackgroundDrawable(); mGrid = (GridView) findViewById(R.id.myGrid); mGrid.setAdapter(new AppsAdapter(this.getApplicationContext(), mApps)); mGrid.setFocusableInTouchMode(true); mGrid.setFocusable(true); mGrid.setSelected(true); mGrid.setClickable(true); mGrid.setOnCreateContextMenuListener(this); mGrid.setOnItemClickListener(listItemClickListener); mGrid.setVisibility(View.VISIBLE); flipper = (ViewFlipper) findViewById(R.id.facebook_start_ui_flipper); facebook_bottom_region = (Button) this.findViewById(R.id.facebook_bottom_region); facebook_bottom_region.setText(R.string.menu_title_notifications); facebook_bottom_region.setOnClickListener(showNotificationClick); facebook_current_pos = (Button) this.findViewById(R.id.facebook_current_pos); setPosition(true); facebook_bottom_region_notification_size = (TextView) this.findViewById(R.id.facebook_bottom_region_notification_size); inboxlayout = (LinearLayout) findViewById(R.id.inboxlayout); facebook_inbox_unread_size_tv = (TextView) findViewById(R.id.facebook_inbox_unread_size_tv); requestlayout = (LinearLayout) findViewById(R.id.requestlayout); facebook_request_unread_size_tv = (TextView) findViewById(R.id.facebook_request_unread_size_tv); eventlayout = (LinearLayout) findViewById(R.id.eventlayout); facebook_request_unread_size_event = (TextView) findViewById(R.id.facebook_request_unread_size_event); facebook_search_span = this.findViewById(R.id.facebook_search_span); facebook_bottom_span = this.findViewById(R.id.facebook_bottom_span); embedded_search_text_editor = (EditText) this.findViewById(R.id.embedded_search_text_editor); // embedded_search_text_editor.setOnFocusChangeListener(focusChangeListener); embedded_search_text_editor.clearFocus(); // embedded_search_text_editor.setFocusable(false); embedded_search_text_editor.setOnClickListener(clickListener); Intent in = new Intent(this.getApplicationContext(), SNSService.class); startService(in); // add to app. init code segment, pops up a warning message if (android.os.Build.DEVICE.trim().toLowerCase().equals(TARGET_DEVICE) || android.os.Build.MODEL.trim().toLowerCase().equals(TARGET_DEVICE2) || android.os.Build.DEVICE.trim().toLowerCase().equals(TARGET_DEVICE_Blu) || android.os.Build.MODEL.trim().toLowerCase().equals(TARGET_DEVICE_vibo_A300) || android.os.Build.DEVICE.trim().toLowerCase().equals(TARGET_DEVICE_vibo_A600)) { // it is under define } else { if (false) { new AlertDialog.Builder(this) .setView(getLinkfiedView(this, R.string.not_target_device)) .setCancelable(true) .setNegativeButton("OK", null) .show(); } } // don't let the input has focus mGrid.requestFocus(); // SocialORM.Account account = orm.getFacebookAccount(); if (checkFacebookAccount(this, account)) { perm_session = loginHelper.getPermanentSesstion(this); if (perm_session != null) { // perm_session.attachActivity(this); facebookA = new AsyncFacebook(perm_session); handler.obtainMessage(FACEBOOK_NOTIFICATION_GET).sendToTarget(); reschedule(); // checkProduct(); } else { launchFacebookLogin(); } } if (false && SystemProperties.getInt("enable_trail", 1) == 1) { trailversion(); } setTitle(); setTitle(title); }
/** * Return if the current radio is LTE on GSM * * @hide */ public static int getLteOnGsmModeStatic() { return SystemProperties.getInt(TelephonyProperties.PROPERTY_LTE_ON_GSM_DEVICE, 0); }
/** Activity manager code dealing with processes. */ class ProcessList { // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. static final int MIN_CRASH_INTERVAL = 60 * 1000; // OOM adjustments for processes in various states: // This is a process only hosting activities that are not visible, // so it can be killed without any disruption. static final int HIDDEN_APP_MAX_ADJ = 15; static int HIDDEN_APP_MIN_ADJ = 9; // The B list of SERVICE_ADJ -- these are the old and decrepit // services that aren't as shiny and interesting as the ones in the A list. static final int SERVICE_B_ADJ = 8; // This is the process of the previous application that the user was in. // This process is kept above other things, because it is very common to // switch back to the previous app. This is important both for recent // task switch (toggling between the two top recent apps) as well as normal // UI flow such as clicking on a URI in the e-mail app to view in the browser, // and then pressing back to return to e-mail. static final int PREVIOUS_APP_ADJ = 7; // This is a process holding the home application -- we want to try // avoiding killing it, even if it would normally be in the background, // because the user interacts with it so much. static final int HOME_APP_ADJ = 6; // This is a process holding an application service -- killing it will not // have much of an impact as far as the user is concerned. static final int SERVICE_ADJ = 5; // This is a process currently hosting a backup operation. Killing it // is not entirely fatal but is generally a bad idea. static final int BACKUP_APP_ADJ = 4; // This is a process with a heavy-weight application. It is in the // background, but we want to try to avoid killing it. Value set in // system/rootdir/init.rc on startup. static final int HEAVY_WEIGHT_APP_ADJ = 3; // This is a process only hosting components that are perceptible to the // user, and we really want to avoid killing them, but they are not // immediately visible. An example is background music playback. static final int PERCEPTIBLE_APP_ADJ = 2; // This is a process only hosting activities that are visible to the // user, so we'd prefer they don't disappear. static final int VISIBLE_APP_ADJ = 1; // This is the process running the current foreground app. We'd really // rather not kill it! static final int FOREGROUND_APP_ADJ = 0; // This is a system persistent process, such as telephony. Definitely // don't want to kill it, but doing so is not completely fatal. static final int PERSISTENT_PROC_ADJ = -12; // The system process runs at the default adjustment. static final int SYSTEM_ADJ = -16; // Memory pages are 4K. static final int PAGE_SIZE = 4 * 1024; // The minimum number of hidden apps we want to be able to keep around, // without empty apps being able to push them out of memory. static final int MIN_HIDDEN_APPS = 2; // The maximum number of hidden processes we will keep around before // killing them; this is just a control to not let us go too crazy with // keeping around processes on devices with large amounts of RAM. static final int MAX_HIDDEN_APPS = SystemProperties.getInt("ro.sys.fw.bg_apps_limit", 24); // We allow empty processes to stick around for at most 30 minutes. static final long MAX_EMPTY_TIME = 30 * 60 * 1000; // The number of hidden at which we don't consider it necessary to do // memory trimming. static final int TRIM_HIDDEN_APPS = 3; // The number of empty apps at which we don't consider it necessary to do // memory trimming. static final int TRIM_EMPTY_APPS = 3; // Threshold of number of hidden+empty where we consider memory critical. static final int TRIM_CRITICAL_THRESHOLD = 3; // Threshold of number of hidden+empty where we consider memory critical. static final int TRIM_LOW_THRESHOLD = 5; // We put empty content processes after any hidden processes that have // been idle for less than 15 seconds. static final long CONTENT_APP_IDLE_OFFSET = 15 * 1000; // We put empty content processes after any hidden processes that have // been idle for less than 120 seconds. static final long EMPTY_APP_IDLE_OFFSET = 120 * 1000; // These are the various interesting memory levels that we will give to // the OOM killer. Note that the OOM killer only supports 6 slots, so we // can't give it a different value for every possible kind of process. private final int[] mOomAdj = new int[] { FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, BACKUP_APP_ADJ, HIDDEN_APP_MIN_ADJ, HIDDEN_APP_MAX_ADJ }; // These are the low-end OOM level limits. This is appropriate for an // HVGA or smaller phone with less than 512MB. Values are in KB. private final long[] mOomMinFreeLow = new long[] { 8192, 12288, 16384, 24576, 28672, 32768 }; // These are the high-end OOM level limits. This is appropriate for a // 1280x800 or larger screen with around 1GB RAM. Values are in KB. private final long[] mOomMinFreeHigh = new long[] { 49152, 61440, 73728, 86016, 98304, 122880 }; // The actual OOM killer memory levels we are using. private final long[] mOomMinFree = new long[mOomAdj.length]; private final long mTotalMemMb; private boolean mHaveDisplaySize; ProcessList() { MemInfoReader minfo = new MemInfoReader(); minfo.readMemInfo(); mTotalMemMb = minfo.getTotalSize() / (1024 * 1024); updateOomLevels(0, 0, false); } void applyDisplaySize(WindowManagerService wm) { if (!mHaveDisplaySize) { Point p = new Point(); wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, p); if (p.x != 0 && p.y != 0) { updateOomLevels(p.x, p.y, true); mHaveDisplaySize = true; } } } private void updateOomLevels(int displayWidth, int displayHeight, boolean write) { // Scale buckets from avail memory: at 300MB we use the lowest values to // 700MB or more for the top values. float scaleMem = ((float) (mTotalMemMb - 300)) / (700 - 300); // Scale buckets from screen size. int minSize = 320 * 480; // 153600 int maxSize = 1280 * 800; // 1024000 230400 870400 .264 float scaleDisp = ((float) (displayWidth * displayHeight) - minSize) / (maxSize - minSize); // Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight); StringBuilder adjString = new StringBuilder(); StringBuilder memString = new StringBuilder(); float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; if (scale < 0) scale = 0; else if (scale > 1) scale = 1; for (int i = 0; i < mOomAdj.length; i++) { long low = mOomMinFreeLow[i]; long high = mOomMinFreeHigh[i]; mOomMinFree[i] = (long) (low + ((high - low) * scale)); if (i > 0) { adjString.append(','); memString.append(','); } adjString.append(mOomAdj[i]); memString.append((mOomMinFree[i] * 1024) / PAGE_SIZE); } // Slog.i("XXXXXXX", "******************************* MINFREE: " + memString); if (write) { writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString()); writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString()); } // GB: 2048,3072,4096,6144,7168,8192 // HC: 8192,10240,12288,14336,16384,20480 } long getMemLevel(int adjustment) { for (int i = 0; i < mOomAdj.length; i++) { if (adjustment <= mOomAdj[i]) { return mOomMinFree[i] * 1024; } } return mOomMinFree[mOomAdj.length - 1] * 1024; } private void writeFile(String path, String data) { FileOutputStream fos = null; try { fos = new FileOutputStream(path); fos.write(data.getBytes()); } catch (IOException e) { Slog.w(ActivityManagerService.TAG, "Unable to write " + path); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { } } } } }
public ScreenRotationAnimation( Context context, SurfaceSession session, boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) { mContext = context; // Screenshot does NOT include rotation! // Allow for abnormal hardware orientation mSnapshotRotation = (4 - android.os.SystemProperties.getInt("ro.sf.hwrotation", 0) / 90) % 4; if (mSnapshotRotation == Surface.ROTATION_0 || mSnapshotRotation == Surface.ROTATION_180) { if (originalRotation == Surface.ROTATION_90 || originalRotation == Surface.ROTATION_270) { mWidth = originalHeight; mHeight = originalWidth; } else { mWidth = originalWidth; mHeight = originalHeight; } } else { if (originalRotation == Surface.ROTATION_90 || originalRotation == Surface.ROTATION_270) { mWidth = originalWidth; mHeight = originalHeight; } else { mWidth = originalHeight; mHeight = originalWidth; } } mOriginalRotation = originalRotation; mOriginalWidth = originalWidth; mOriginalHeight = originalHeight; if (!inTransaction) { if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION ScreenRotationAnimation"); Surface.openTransaction(); } try { try { mSurface = new Surface( session, 0, "FreezeSurface", -1, mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); if (mSurface == null || !mSurface.isValid()) { // Screenshot failed, punt. mSurface = null; return; } mSurface.setLayer(FREEZE_LAYER + 1); mSurface.show(); } catch (Surface.OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); } if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " FREEZE " + mSurface + ": CREATE"); setRotation(originalRotation); } finally { if (!inTransaction) { Surface.closeTransaction(); if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "<<< CLOSE TRANSACTION ScreenRotationAnimation"); } } }
/** Ringer manager for the Phone app. */ public class Ringer { private static final String LOG_TAG = "Ringer"; private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); private static final int PLAY_RING_ONCE = 1; private static final int STOP_RING = 3; private static final int VIBRATE_LENGTH = 1000; // ms private static final int PAUSE_LENGTH = 1000; // ms // Uri for the ringtone. Uri mCustomRingtoneUri; Ringtone mRingtone; Vibrator mVibrator = new Vibrator(); IHardwareService mHardwareService; volatile boolean mContinueVibrating; VibratorThread mVibratorThread; Context mContext; private Worker mRingThread; private Handler mRingHandler; private long mFirstRingEventTime = -1; private long mFirstRingStartTime = -1; Ringer(Phone phone) { mContext = phone.getContext(); mHardwareService = IHardwareService.Stub.asInterface(ServiceManager.getService("hardware")); } /** * After a radio technology change, e.g. from CDMA to GSM or vice versa, the Context of the Ringer * has to be updated. This is done by that function. * * @parameter Phone, the new active phone for the appropriate radio technology */ void updateRingerContextAfterRadioTechnologyChange(Phone phone) { if (DBG) Log.d(LOG_TAG, "updateRingerContextAfterRadioTechnologyChange..."); mContext = phone.getContext(); } /** * @return true if we're playing a ringtone and/or vibrating to indicate that there's an incoming * call. ("Ringing" here is used in the general sense. If you literally need to know if we're * playing a ringtone or vibrating, use isRingtonePlaying() or isVibrating() instead.) * @see isVibrating * @see isRingtonePlaying */ boolean isRinging() { synchronized (this) { return (isRingtonePlaying() || isVibrating()); } } /** * @return true if the ringtone is playing * @see isVibrating * @see isRinging */ private boolean isRingtonePlaying() { synchronized (this) { return (mRingtone != null && mRingtone.isPlaying()) || (mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE)); } } /** * @return true if we're vibrating in response to an incoming call * @see isVibrating * @see isRinging */ private boolean isVibrating() { synchronized (this) { return (mVibratorThread != null); } } /** Starts the ringtone and/or vibrator */ void ring() { if (DBG) log("ring()..."); synchronized (this) { try { mHardwareService.setAttentionLight(true); } catch (RemoteException ex) { // the other end of this binder call is in the system process. } if (shouldVibrate() && mVibratorThread == null) { mContinueVibrating = true; mVibratorThread = new VibratorThread(); if (DBG) log("- starting vibrator..."); mVibratorThread.start(); } AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) { if (DBG) log("skipping ring because volume is zero"); return; } makeLooper(); if (mFirstRingEventTime < 0) { mFirstRingEventTime = SystemClock.elapsedRealtime(); mRingHandler.sendEmptyMessage(PLAY_RING_ONCE); } else { // For repeat rings, figure out by how much to delay // the ring so that it happens the correct amount of // time after the previous ring if (mFirstRingStartTime > 0) { // Delay subsequent rings by the delta between event // and play time of the first ring if (DBG) { log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime)); } mRingHandler.sendEmptyMessageDelayed( PLAY_RING_ONCE, mFirstRingStartTime - mFirstRingEventTime); // Math.min(mRingtone.getDuration(), 2000)); } else { // We've gotten two ring events so far, but the ring // still hasn't started. Reset the event time to the // time of this event to maintain correct spacing. mFirstRingEventTime = SystemClock.elapsedRealtime(); } } } } boolean shouldVibrate() { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER); } /** Stops the ringtone and/or vibrator if any of these are actually ringing/vibrating. */ void stopRing() { synchronized (this) { if (DBG) log("stopRing()..."); try { mHardwareService.setAttentionLight(false); } catch (RemoteException ex) { // the other end of this binder call is in the system process. } if (mRingHandler != null) { mRingHandler.removeCallbacksAndMessages(null); Message msg = mRingHandler.obtainMessage(STOP_RING); msg.obj = mRingtone; mRingHandler.sendMessage(msg); PhoneUtils.setAudioMode(mContext, AudioManager.MODE_NORMAL); mRingThread = null; mRingHandler = null; mRingtone = null; mFirstRingEventTime = -1; mFirstRingStartTime = -1; } else { if (DBG) log("- stopRing: null mRingHandler!"); } if (mVibratorThread != null) { if (DBG) log("- stopRing: cleaning up vibrator thread..."); mContinueVibrating = false; mVibratorThread = null; } // Also immediately cancel any vibration in progress. mVibrator.cancel(); } } private class VibratorThread extends Thread { public void run() { while (mContinueVibrating) { mVibrator.vibrate(VIBRATE_LENGTH); SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH); } } } private class Worker implements Runnable { private final Object mLock = new Object(); private Looper mLooper; Worker(String name) { Thread t = new Thread(null, this, name); t.start(); synchronized (mLock) { while (mLooper == null) { try { mLock.wait(); } catch (InterruptedException ex) { } } } } public Looper getLooper() { return mLooper; } public void run() { synchronized (mLock) { Looper.prepare(); mLooper = Looper.myLooper(); mLock.notifyAll(); } Looper.loop(); } public void quit() { mLooper.quit(); } } /** * Sets the ringtone uri in preparation for ringtone creation in makeLooper(). This uri is * defaulted to the phone-wide default ringtone. */ void setCustomRingtoneUri(Uri uri) { if (uri != null) { mCustomRingtoneUri = uri; } } private void makeLooper() { if (mRingThread == null) { mRingThread = new Worker("ringer"); mRingHandler = new Handler(mRingThread.getLooper()) { @Override public void handleMessage(Message msg) { Ringtone r = null; switch (msg.what) { case PLAY_RING_ONCE: if (DBG) log("mRingHandler: PLAY_RING_ONCE..."); if (mRingtone == null && !hasMessages(STOP_RING)) { // create the ringtone with the uri if (DBG) log("creating ringtone: " + mCustomRingtoneUri); r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri); synchronized (Ringer.this) { if (!hasMessages(STOP_RING)) { mRingtone = r; } } } r = mRingtone; if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) { PhoneUtils.setAudioMode(mContext, AudioManager.MODE_RINGTONE); r.play(); synchronized (Ringer.this) { if (mFirstRingStartTime < 0) { mFirstRingStartTime = SystemClock.elapsedRealtime(); } } } break; case STOP_RING: if (DBG) log("mRingHandler: STOP_RING..."); r = (Ringtone) msg.obj; if (r != null) { r.stop(); } else { if (DBG) log("- STOP_RING with null ringtone! msg = " + msg); } getLooper().quit(); break; } } }; } } private static void log(String msg) { Log.d(LOG_TAG, msg); } }
public class SamsungRIL extends RIL implements CommandsInterface { private boolean mSignalbarCount = SystemProperties.getInt("ro.telephony.sends_barcount", 0) == 1 ? true : false; private boolean mIsSamsungCdma = SystemProperties.getBoolean("ro.ril.samsung_cdma", false); public SamsungRIL(Context context, int networkMode, int cdmaSubscription) { super(context, networkMode, cdmaSubscription); } // SAMSUNG SGS STATES static final int RIL_UNSOL_STK_SEND_SMS_RESULT = 11002; static final int RIL_UNSOL_O2_HOME_ZONE_INFO = 11007; static final int RIL_UNSOL_DEVICE_READY_NOTI = 11008; static final int RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST_3 = 11010; static final int RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST_2 = 11011; static final int RIL_UNSOL_HSDPA_STATE_CHANGED = 11016; static final int RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST = 11012; static final int RIL_REQUEST_DIAL_EMERGENCY = 10016; @Override public void setRadioPower(boolean on, Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_RADIO_POWER, result); // samsung crap for airplane mode if (on) { rr.mp.writeInt(1); rr.mp.writeInt(1); } else { rr.mp.writeInt(2); rr.mp.writeInt(0); rr.mp.writeInt(0); } if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); send(rr); } @Override protected void processSolicited(Parcel p) { int serial, error; boolean found = false; serial = p.readInt(); error = p.readInt(); Log.d(LOG_TAG, "Serial: " + serial); Log.d(LOG_TAG, "Error: " + error); RILRequest rr; rr = findAndRemoveRequestFromList(serial); if (rr == null) { Log.w(LOG_TAG, "Unexpected solicited response! sn: " + serial + " error: " + error); return; } Object ret = null; if (error == 0 || p.dataAvail() > 0) { // either command succeeds or command fails but with data payload try { switch (rr.mRequest) { /* cat libs/telephony/ril_commands.h \ | egrep "^ *{RIL_" \ | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: ret = \2(p); break;/' */ case RIL_REQUEST_GET_SIM_STATUS: ret = responseIccCardStatus(p); break; case RIL_REQUEST_ENTER_SIM_PIN: ret = responseInts(p); break; case RIL_REQUEST_ENTER_SIM_PUK: ret = responseInts(p); break; case RIL_REQUEST_ENTER_SIM_PIN2: ret = responseInts(p); break; case RIL_REQUEST_ENTER_SIM_PUK2: ret = responseInts(p); break; case RIL_REQUEST_CHANGE_SIM_PIN: ret = responseInts(p); break; case RIL_REQUEST_CHANGE_SIM_PIN2: ret = responseInts(p); break; case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION: ret = responseInts(p); break; case RIL_REQUEST_GET_CURRENT_CALLS: ret = responseCallList(p); break; case RIL_REQUEST_DIAL: ret = responseVoid(p); break; case RIL_REQUEST_GET_IMSI: ret = responseString(p); break; case RIL_REQUEST_HANGUP: ret = responseVoid(p); break; case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: ret = responseVoid(p); break; case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND: ret = responseVoid(p); break; case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE: ret = responseVoid(p); break; case RIL_REQUEST_CONFERENCE: ret = responseVoid(p); break; case RIL_REQUEST_UDUB: ret = responseVoid(p); break; case RIL_REQUEST_LAST_CALL_FAIL_CAUSE: ret = responseInts(p); break; case RIL_REQUEST_SIGNAL_STRENGTH: ret = responseSignalStrength(p); break; case RIL_REQUEST_VOICE_REGISTRATION_STATE: ret = responseStrings(p); break; case RIL_REQUEST_DATA_REGISTRATION_STATE: ret = responseStrings(p); break; case RIL_REQUEST_OPERATOR: ret = responseStrings(p); break; case RIL_REQUEST_RADIO_POWER: ret = responseVoid(p); break; case RIL_REQUEST_DTMF: ret = responseVoid(p); break; case RIL_REQUEST_SEND_SMS: ret = responseSMS(p); break; case RIL_REQUEST_SEND_SMS_EXPECT_MORE: ret = responseSMS(p); break; case RIL_REQUEST_SETUP_DATA_CALL: ret = responseSetupDataCall(p); break; case RIL_REQUEST_SIM_IO: ret = responseICC_IO(p); break; case RIL_REQUEST_SEND_USSD: ret = responseVoid(p); break; case RIL_REQUEST_CANCEL_USSD: ret = responseVoid(p); break; case RIL_REQUEST_GET_CLIR: ret = responseInts(p); break; case RIL_REQUEST_SET_CLIR: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_CALL_FORWARD_STATUS: ret = responseCallForward(p); break; case RIL_REQUEST_SET_CALL_FORWARD: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_CALL_WAITING: ret = responseInts(p); break; case RIL_REQUEST_SET_CALL_WAITING: ret = responseVoid(p); break; case RIL_REQUEST_SMS_ACKNOWLEDGE: ret = responseVoid(p); break; case RIL_REQUEST_GET_IMEI: ret = responseString(p); break; case RIL_REQUEST_GET_IMEISV: ret = responseString(p); break; case RIL_REQUEST_ANSWER: ret = responseVoid(p); break; case RIL_REQUEST_DEACTIVATE_DATA_CALL: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_FACILITY_LOCK: ret = responseInts(p); break; case RIL_REQUEST_SET_FACILITY_LOCK: ret = responseInts(p); break; case RIL_REQUEST_CHANGE_BARRING_PASSWORD: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE: ret = responseInts(p); break; case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC: ret = responseVoid(p); break; case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS: ret = responseOperatorInfos(p); break; case RIL_REQUEST_DTMF_START: ret = responseVoid(p); break; case RIL_REQUEST_DTMF_STOP: ret = responseVoid(p); break; case RIL_REQUEST_BASEBAND_VERSION: ret = responseString(p); break; case RIL_REQUEST_SEPARATE_CONNECTION: ret = responseVoid(p); break; case RIL_REQUEST_SET_MUTE: ret = responseVoid(p); break; case RIL_REQUEST_GET_MUTE: ret = responseInts(p); break; case RIL_REQUEST_QUERY_CLIP: ret = responseInts(p); break; case RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE: ret = responseInts(p); break; case RIL_REQUEST_DATA_CALL_LIST: ret = responseDataCallList(p); break; case RIL_REQUEST_RESET_RADIO: ret = responseVoid(p); break; case RIL_REQUEST_OEM_HOOK_RAW: ret = responseRaw(p); break; case RIL_REQUEST_OEM_HOOK_STRINGS: ret = responseStrings(p); break; case RIL_REQUEST_SCREEN_STATE: ret = responseVoid(p); break; case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION: ret = responseVoid(p); break; case RIL_REQUEST_WRITE_SMS_TO_SIM: ret = responseInts(p); break; case RIL_REQUEST_DELETE_SMS_ON_SIM: ret = responseVoid(p); break; case RIL_REQUEST_SET_BAND_MODE: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE: ret = responseInts(p); break; case RIL_REQUEST_STK_GET_PROFILE: ret = responseString(p); break; case RIL_REQUEST_STK_SET_PROFILE: ret = responseVoid(p); break; case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND: ret = responseString(p); break; case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE: ret = responseVoid(p); break; case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM: ret = responseInts(p); break; case RIL_REQUEST_EXPLICIT_CALL_TRANSFER: ret = responseVoid(p); break; case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE: ret = responseVoid(p); break; case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE: ret = responseNetworkType(p); break; case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS: ret = responseCellList(p); break; case RIL_REQUEST_SET_LOCATION_UPDATES: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE: ret = responseInts(p); break; case RIL_REQUEST_SET_TTY_MODE: ret = responseVoid(p); break; case RIL_REQUEST_QUERY_TTY_MODE: ret = responseInts(p); break; case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE: ret = responseInts(p); break; case RIL_REQUEST_CDMA_FLASH: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_BURST_DTMF: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_SEND_SMS: ret = responseSMS(p); break; case RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE: ret = responseVoid(p); break; case RIL_REQUEST_GSM_GET_BROADCAST_CONFIG: ret = responseGmsBroadcastConfig(p); break; case RIL_REQUEST_GSM_SET_BROADCAST_CONFIG: ret = responseVoid(p); break; case RIL_REQUEST_GSM_BROADCAST_ACTIVATION: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG: ret = responseCdmaBroadcastConfig(p); break; case RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_BROADCAST_ACTIVATION: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY: ret = responseVoid(p); break; case RIL_REQUEST_CDMA_SUBSCRIPTION: ret = responseStrings(p); break; case RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM: ret = responseInts(p); break; case RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM: ret = responseVoid(p); break; case RIL_REQUEST_DEVICE_IDENTITY: ret = responseStrings(p); break; case RIL_REQUEST_GET_SMSC_ADDRESS: ret = responseString(p); break; case RIL_REQUEST_SET_SMSC_ADDRESS: ret = responseVoid(p); break; case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break; case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS: ret = responseVoid(p); break; case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING: ret = responseVoid(p); break; default: throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest); // break; } } catch (Throwable tr) { // Exceptions here usually mean invalid RIL responses Log.w( LOG_TAG, rr.serialString() + "< " + requestToString(rr.mRequest) + " exception, possible invalid RIL response", tr); if (rr.mResult != null) { AsyncResult.forMessage(rr.mResult, null, tr); rr.mResult.sendToTarget(); } rr.release(); return; } } if (error != 0) { // ugly fix for Samsung messing up SMS_SEND request fail in binary RIL if (!(error == -1 && rr.mRequest == RIL_REQUEST_SEND_SMS)) { rr.onError(error, ret); rr.release(); return; } else { try { ret = responseSMS(p); } catch (Throwable tr) { Log.w( LOG_TAG, rr.serialString() + "< " + requestToString(rr.mRequest) + " exception, Processing Samsung SMS fix ", tr); rr.onError(error, ret); rr.release(); return; } } } if (RILJ_LOGD) riljLog( rr.serialString() + "< " + requestToString(rr.mRequest) + " " + retToString(rr.mRequest, ret)); if (rr.mResult != null) { AsyncResult.forMessage(rr.mResult, ret, null); rr.mResult.sendToTarget(); } rr.release(); } @Override public void dial(String address, int clirMode, UUSInfo uusInfo, Message result) { RILRequest rr; if (PhoneNumberUtils.isEmergencyNumber(address)) { Log.v(LOG_TAG, "Emergency dial: " + address); rr = RILRequest.obtain(RIL_REQUEST_DIAL_EMERGENCY, result); rr.mp.writeString(address + "/"); } else { rr = RILRequest.obtain(RIL_REQUEST_DIAL, result); rr.mp.writeString(address); } rr.mp.writeInt(clirMode); rr.mp.writeInt(0); // UUS information is absent if (uusInfo == null) { rr.mp.writeInt(0); // UUS information is absent } else { rr.mp.writeInt(1); // UUS information is present rr.mp.writeInt(uusInfo.getType()); rr.mp.writeInt(uusInfo.getDcs()); rr.mp.writeByteArray(uusInfo.getUserData()); } if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); send(rr); } @Override protected void processUnsolicited(Parcel p) { int response; Object ret; response = p.readInt(); try { switch (response) { /* cat libs/telephony/ril_unsol_commands.h \ | egrep "^ *{RIL_" \ | sed -re 's/\{([^,]+),[^,]+,([^}]+).+/case \1: \2(rr, p); break;/' */ case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED: ret = responseVoid(p); break; case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: ret = responseVoid(p); break; case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: ret = responseVoid(p); break; case RIL_UNSOL_RESPONSE_NEW_SMS: ret = responseString(p); break; case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT: ret = responseString(p); break; case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM: ret = responseInts(p); break; case RIL_UNSOL_ON_USSD: ret = responseStrings(p); break; case RIL_UNSOL_NITZ_TIME_RECEIVED: ret = responseString(p); break; case RIL_UNSOL_SIGNAL_STRENGTH: ret = responseSignalStrength(p); break; case RIL_UNSOL_DATA_CALL_LIST_CHANGED: ret = responseDataCallList(p); break; case RIL_UNSOL_SUPP_SVC_NOTIFICATION: ret = responseSuppServiceNotification(p); break; case RIL_UNSOL_STK_SESSION_END: ret = responseVoid(p); break; case RIL_UNSOL_STK_PROACTIVE_COMMAND: ret = responseString(p); break; case RIL_UNSOL_STK_EVENT_NOTIFY: ret = responseString(p); break; case RIL_UNSOL_STK_CALL_SETUP: ret = responseInts(p); break; case RIL_UNSOL_SIM_SMS_STORAGE_FULL: ret = responseVoid(p); break; case RIL_UNSOL_SIM_REFRESH: ret = responseInts(p); break; case RIL_UNSOL_CALL_RING: ret = responseCallRing(p); break; case RIL_UNSOL_RESTRICTED_STATE_CHANGED: ret = responseInts(p); break; case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: ret = responseVoid(p); break; case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS: ret = responseCdmaSms(p); break; case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS: ret = responseString(p); break; case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL: ret = responseVoid(p); break; case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE: ret = responseVoid(p); break; case RIL_UNSOL_CDMA_CALL_WAITING: ret = responseCdmaCallWaiting(p); break; case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS: ret = responseInts(p); break; case RIL_UNSOL_CDMA_INFO_REC: ret = responseCdmaInformationRecord(p); break; case RIL_UNSOL_OEM_HOOK_RAW: ret = responseRaw(p); break; case RIL_UNSOL_RINGBACK_TONE: ret = responseInts(p); break; case RIL_UNSOL_RESEND_INCALL_MUTE: ret = responseVoid(p); break; case RIL_UNSOL_HSDPA_STATE_CHANGED: ret = responseVoid(p); break; // fixing anoying Exceptions caused by the new Samsung states // FIXME figure out what the states mean an what data is in the parcel case RIL_UNSOL_O2_HOME_ZONE_INFO: ret = responseVoid(p); break; case RIL_UNSOL_STK_SEND_SMS_RESULT: ret = responseVoid(p); break; case RIL_UNSOL_DEVICE_READY_NOTI: ret = responseVoid(p); break; case RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST: ret = responseVoid(p); break; case RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST_2: ret = responseVoid(p); break; case RIL_UNSOL_SAMSUNG_UNKNOWN_MAGIC_REQUEST_3: ret = responseVoid(p); break; default: throw new RuntimeException("Unrecognized unsol response: " + response); // break; (implied) } } catch (Throwable tr) { Log.e( LOG_TAG, "Exception processing unsol response: " + response + "Exception:" + tr.toString()); return; } switch (response) { case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED: /* has bonus radio state int */ RadioState newState = getRadioStateFromInt(p.readInt()); if (RILJ_LOGD) unsljLogMore(response, newState.toString()); switchToRadioState(newState); break; case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED: if (RILJ_LOGD) unsljLog(response); mCallStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); break; case RIL_UNSOL_HSDPA_STATE_CHANGED: if (RILJ_LOGD) unsljLog(response); mVoiceNetworkStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); break; case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: if (RILJ_LOGD) unsljLog(response); mVoiceNetworkStateRegistrants.notifyRegistrants(new AsyncResult(null, null, null)); break; case RIL_UNSOL_RESPONSE_NEW_SMS: { if (RILJ_LOGD) unsljLog(response); // FIXME this should move up a layer String a[] = new String[2]; a[1] = (String) ret; SmsMessage sms; sms = SmsMessage.newFromCMT(a); if (mGsmSmsRegistrant != null) { mGsmSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null)); } break; } case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT: if (RILJ_LOGD) unsljLogRet(response, ret); if (mSmsStatusRegistrant != null) { mSmsStatusRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM: if (RILJ_LOGD) unsljLogRet(response, ret); int[] smsIndex = (int[]) ret; if (smsIndex.length == 1) { if (mSmsOnSimRegistrant != null) { mSmsOnSimRegistrant.notifyRegistrant(new AsyncResult(null, smsIndex, null)); } } else { if (RILJ_LOGD) riljLog(" NEW_SMS_ON_SIM ERROR with wrong length " + smsIndex.length); } break; case RIL_UNSOL_ON_USSD: String[] resp = (String[]) ret; if (resp.length < 2) { resp = new String[2]; resp[0] = ((String[]) ret)[0]; resp[1] = null; } if (RILJ_LOGD) unsljLogMore(response, resp[0]); if (mUSSDRegistrant != null) { mUSSDRegistrant.notifyRegistrant(new AsyncResult(null, resp, null)); } break; case RIL_UNSOL_NITZ_TIME_RECEIVED: if (RILJ_LOGD) unsljLogRet(response, ret); // has bonus long containing milliseconds since boot that the NITZ // time was received long nitzReceiveTime = p.readLong(); Object[] result = new Object[2]; String nitz = (String) ret; if (RILJ_LOGD) riljLog(" RIL_UNSOL_NITZ_TIME_RECEIVED length = " + nitz.split("[/:,+-]").length); // remove the tailing information that samsung added to the string // it will screw the NITZ parser if (nitz.split("[/:,+-]").length >= 9) nitz = nitz.substring(0, (nitz.lastIndexOf(","))); if (RILJ_LOGD) riljLog(" RIL_UNSOL_NITZ_TIME_RECEIVED striped nitz = " + nitz); result[0] = nitz; result[1] = Long.valueOf(nitzReceiveTime); if (mNITZTimeRegistrant != null) { mNITZTimeRegistrant.notifyRegistrant(new AsyncResult(null, result, null)); } else { // in case NITZ time registrant isnt registered yet mLastNITZTimeInfo = nitz; } break; case RIL_UNSOL_SIGNAL_STRENGTH: // Note this is set to "verbose" because it happens // frequently if (RILJ_LOGV) unsljLogvRet(response, ret); if (mSignalStrengthRegistrant != null) { mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_DATA_CALL_LIST_CHANGED: if (RILJ_LOGD) unsljLogRet(response, ret); mDataNetworkStateRegistrants.notifyRegistrants(new AsyncResult(null, ret, null)); break; case RIL_UNSOL_SUPP_SVC_NOTIFICATION: if (RILJ_LOGD) unsljLogRet(response, ret); if (mSsnRegistrant != null) { mSsnRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_STK_SESSION_END: if (RILJ_LOGD) unsljLog(response); if (mCatSessionEndRegistrant != null) { mCatSessionEndRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_STK_PROACTIVE_COMMAND: if (RILJ_LOGD) unsljLogRet(response, ret); if (mCatProCmdRegistrant != null) { mCatProCmdRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_STK_EVENT_NOTIFY: if (RILJ_LOGD) unsljLogRet(response, ret); if (mCatEventRegistrant != null) { mCatEventRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_STK_CALL_SETUP: if (RILJ_LOGD) unsljLogRet(response, ret); if (mCatCallSetUpRegistrant != null) { mCatCallSetUpRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_SIM_SMS_STORAGE_FULL: if (RILJ_LOGD) unsljLog(response); if (mIccSmsFullRegistrant != null) { mIccSmsFullRegistrant.notifyRegistrant(); } break; case RIL_UNSOL_SIM_REFRESH: if (RILJ_LOGD) unsljLogRet(response, ret); if (mIccRefreshRegistrants != null) { mIccRefreshRegistrants.notifyRegistrants(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_CALL_RING: if (RILJ_LOGD) unsljLogRet(response, ret); if (mRingRegistrant != null) { mRingRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_RESTRICTED_STATE_CHANGED: if (RILJ_LOGD) unsljLogvRet(response, ret); if (mRestrictedStateRegistrant != null) { mRestrictedStateRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED: if (RILJ_LOGD) unsljLog(response); if (mIccStatusChangedRegistrants != null) { mIccStatusChangedRegistrants.notifyRegistrants(); } break; case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS: if (RILJ_LOGD) unsljLog(response); SmsMessage sms = (SmsMessage) ret; if (mCdmaSmsRegistrant != null) { mCdmaSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null)); } break; case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS: if (RILJ_LOGD) unsljLog(response); if (mGsmBroadcastSmsRegistrant != null) { mGsmBroadcastSmsRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL: if (RILJ_LOGD) unsljLog(response); if (mIccSmsFullRegistrant != null) { mIccSmsFullRegistrant.notifyRegistrant(); } break; case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE: if (RILJ_LOGD) unsljLog(response); if (mEmergencyCallbackModeRegistrant != null) { mEmergencyCallbackModeRegistrant.notifyRegistrant(); } break; case RIL_UNSOL_CDMA_CALL_WAITING: if (RILJ_LOGD) unsljLogRet(response, ret); if (mCallWaitingInfoRegistrants != null) { mCallWaitingInfoRegistrants.notifyRegistrants(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS: if (RILJ_LOGD) unsljLogRet(response, ret); if (mOtaProvisionRegistrants != null) { mOtaProvisionRegistrants.notifyRegistrants(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_CDMA_INFO_REC: ArrayList<CdmaInformationRecords> listInfoRecs; try { listInfoRecs = (ArrayList<CdmaInformationRecords>) ret; } catch (ClassCastException e) { Log.e(LOG_TAG, "Unexpected exception casting to listInfoRecs", e); break; } for (CdmaInformationRecords rec : listInfoRecs) { if (RILJ_LOGD) unsljLogRet(response, rec); notifyRegistrantsCdmaInfoRec(rec); } break; case RIL_UNSOL_OEM_HOOK_RAW: if (RILJ_LOGD) unsljLogvRet(response, IccUtils.bytesToHexString((byte[]) ret)); if (mUnsolOemHookRawRegistrant != null) { mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, ret, null)); } break; case RIL_UNSOL_RINGBACK_TONE: if (RILJ_LOGD) unsljLogvRet(response, ret); if (mRingbackToneRegistrants != null) { boolean playtone = (((int[]) ret)[0] == 1); mRingbackToneRegistrants.notifyRegistrants(new AsyncResult(null, playtone, null)); } break; case RIL_UNSOL_RESEND_INCALL_MUTE: if (RILJ_LOGD) unsljLogRet(response, ret); if (mResendIncallMuteRegistrants != null) { mResendIncallMuteRegistrants.notifyRegistrants(new AsyncResult(null, ret, null)); } } } @Override protected Object responseCallList(Parcel p) { int num; int voiceSettings; ArrayList<DriverCall> response; DriverCall dc; int dataAvail = p.dataAvail(); int pos = p.dataPosition(); int size = p.dataSize(); Log.d(LOG_TAG, "Parcel size = " + size); Log.d(LOG_TAG, "Parcel pos = " + pos); Log.d(LOG_TAG, "Parcel dataAvail = " + dataAvail); // Samsung f****d up here num = p.readInt(); Log.d(LOG_TAG, "num = " + num); response = new ArrayList<DriverCall>(num); for (int i = 0; i < num; i++) { if (mIsSamsungCdma) dc = new SamsungDriverCall(); else dc = new DriverCall(); dc.state = DriverCall.stateFromCLCC(p.readInt()); Log.d(LOG_TAG, "state = " + dc.state); dc.index = p.readInt(); Log.d(LOG_TAG, "index = " + dc.index); dc.TOA = p.readInt(); Log.d(LOG_TAG, "state = " + dc.TOA); dc.isMpty = (0 != p.readInt()); Log.d(LOG_TAG, "isMpty = " + dc.isMpty); dc.isMT = (0 != p.readInt()); Log.d(LOG_TAG, "isMT = " + dc.isMT); dc.als = p.readInt(); Log.d(LOG_TAG, "als = " + dc.als); voiceSettings = p.readInt(); dc.isVoice = (0 == voiceSettings) ? false : true; Log.d(LOG_TAG, "isVoice = " + dc.isVoice); dc.isVoicePrivacy = (0 != p.readInt()); // Some Samsung magic data for Videocalls voiceSettings = p.readInt(); // printing it to cosole for later investigation Log.d(LOG_TAG, "Samsung magic = " + voiceSettings); dc.number = p.readString(); Log.d(LOG_TAG, "number = " + dc.number); int np = p.readInt(); Log.d(LOG_TAG, "np = " + np); dc.numberPresentation = DriverCall.presentationFromCLIP(np); dc.name = p.readString(); Log.d(LOG_TAG, "name = " + dc.name); dc.namePresentation = p.readInt(); Log.d(LOG_TAG, "namePresentation = " + dc.namePresentation); int uusInfoPresent = p.readInt(); Log.d(LOG_TAG, "uusInfoPresent = " + uusInfoPresent); if (uusInfoPresent == 1) { dc.uusInfo = new UUSInfo(); dc.uusInfo.setType(p.readInt()); dc.uusInfo.setDcs(p.readInt()); byte[] userData = p.createByteArray(); dc.uusInfo.setUserData(userData); Log.v( LOG_TAG, String.format( "Incoming UUS : type=%d, dcs=%d, length=%d", dc.uusInfo.getType(), dc.uusInfo.getDcs(), dc.uusInfo.getUserData().length)); Log.v(LOG_TAG, "Incoming UUS : data (string)=" + new String(dc.uusInfo.getUserData())); Log.v( LOG_TAG, "Incoming UUS : data (hex): " + IccUtils.bytesToHexString(dc.uusInfo.getUserData())); } else { Log.v(LOG_TAG, "Incoming UUS : NOT present!"); } // Make sure there's a leading + on addresses with a TOA of 145 dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA); response.add(dc); if (dc.isVoicePrivacy) { mVoicePrivacyOnRegistrants.notifyRegistrants(); Log.d(LOG_TAG, "InCall VoicePrivacy is enabled"); } else { mVoicePrivacyOffRegistrants.notifyRegistrants(); Log.d(LOG_TAG, "InCall VoicePrivacy is disabled"); } } Collections.sort(response); return response; } @Override protected Object responseSignalStrength(Parcel p) { int numInts = 12; int response[]; /* TODO: Add SignalStrength class to match RIL_SignalStrength */ response = new int[numInts]; for (int i = 0; i < 7; i++) { response[i] = p.readInt(); } // SamsungRIL is a v3 RIL, fill the rest with -1 for (int i = 7; i < numInts; i++) { response[i] = -1; } /* Matching Samsung signal strength to asu. Method taken from Samsungs cdma/gsmSignalStateTracker */ if (mSignalbarCount) { // Samsung sends the count of bars that should be displayed instead of // a real signal strength response[0] = ((response[0] & 0xFF00) >> 8) * 3; // gsmDbm } else { response[0] = response[0] & 0xFF; // gsmDbm } response[1] = -1; // gsmEcio response[2] = (response[2] < 0) ? -120 : -response[2]; // cdmaDbm response[3] = (response[3] < 0) ? -160 : -response[3]; // cdmaEcio response[4] = (response[4] < 0) ? -120 : -response[4]; // evdoRssi response[5] = (response[5] < 0) ? -1 : -response[5]; // evdoEcio if (response[6] < 0 || response[6] > 8) response[6] = -1; return response; } protected Object responseNetworkType(Parcel p) { int response[] = (int[]) responseInts(p); // When the modem responds Phone.NT_MODE_GLOBAL, it means Phone.NT_MODE_WCDMA_PREF if (!mIsSamsungCdma && response[0] == Phone.NT_MODE_GLOBAL) { Log.d(LOG_TAG, "Overriding network type response from global to WCDMA preferred"); response[0] = Phone.NT_MODE_WCDMA_PREF; } return response; } @Override protected Object responseSetupDataCall(Parcel p) { DataCallState dataCall = new DataCallState(); String strings[] = (String[]) responseStrings(p); if (strings.length >= 2) { dataCall.cid = Integer.parseInt(strings[0]); dataCall.ifname = strings[1]; if (strings.length >= 3) { dataCall.addresses = strings[2].split(" "); } } else { dataCall.status = FailCause.ERROR_UNSPECIFIED.getErrorCode(); // Who knows? } return dataCall; } protected class SamsungDriverCall extends DriverCall { @Override public String toString() { // Samsung CDMA devices' call parcel is formatted differently // fake unused data for video calls, and fix formatting // so that voice calls' information can be correctly parsed return "id=" + index + "," + state + "," + "toa=" + TOA + "," + (isMpty ? "conf" : "norm") + "," + (isMT ? "mt" : "mo") + "," + "als=" + als + "," + (isVoice ? "voc" : "nonvoc") + "," + "nonvid" + "," + number + "," + "cli=" + numberPresentation + "," + "name=" + name + "," + namePresentation; } } /** {@inheritDoc} */ @Override public void setCurrentPreferredNetworkType() { if (RILJ_LOGD) riljLog("setCurrentPreferredNetworkType IGNORED"); /* Google added this as a fix for crespo loosing network type after * taking an OTA. This messes up the data connection state for us * due to the way we handle network type change (disable data * then change then re-enable). */ } @Override public void setPreferredNetworkType(int networkType, Message response) { /* Samsung modem implementation does bad things when a datacall is running * while switching the preferred networktype. */ ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm.getMobileDataEnabled()) { ConnectivityHandler handler = new ConnectivityHandler(mContext); handler.setPreferedNetworkType(networkType, response); } else { sendPreferedNetworktype(networkType, response); } } // Sends the real RIL request to the modem. private void sendPreferedNetworktype(int networkType, Message response) { RILRequest rr = RILRequest.obtain(RILConstants.RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, response); rr.mp.writeInt(1); rr.mp.writeInt(networkType); if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " : " + networkType); send(rr); } /* private class that does the handling for the dataconnection * dataconnection is done async, so we send the request for disabling it, * wait for the response, set the prefered networktype and notify the * real sender with its result. */ private class ConnectivityHandler extends Handler { private static final int MESSAGE_SET_PREFERRED_NETWORK_TYPE = 30; private Context mContext; private int mDesiredNetworkType; // the original message, we need it for calling back the original caller when done private Message mNetworktypeResponse; private ConnectivityBroadcastReceiver mConnectivityReceiver = new ConnectivityBroadcastReceiver(); public ConnectivityHandler(Context context) { mContext = context; } private void startListening() { IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mConnectivityReceiver, filter); } private synchronized void stopListening() { mContext.unregisterReceiver(mConnectivityReceiver); } public void setPreferedNetworkType(int networkType, Message response) { Log.d(LOG_TAG, "Mobile Dataconnection is online setting it down"); mDesiredNetworkType = networkType; mNetworktypeResponse = response; ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); // start listening for the connectivity change broadcast startListening(); cm.setMobileDataEnabled(false); } @Override public void handleMessage(Message msg) { switch (msg.what) { // networktype was set, now we can enable the dataconnection again case MESSAGE_SET_PREFERRED_NETWORK_TYPE: ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); Log.d(LOG_TAG, "preferred NetworkType set upping Mobile Dataconnection"); cm.setMobileDataEnabled(true); // everything done now call back that we have set the networktype AsyncResult.forMessage(mNetworktypeResponse, null, null); mNetworktypeResponse.sendToTarget(); mNetworktypeResponse = null; break; default: throw new RuntimeException("unexpected event not handled"); } } private class ConnectivityBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { Log.w(LOG_TAG, "onReceived() called with " + intent); return; } boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); if (noConnectivity) { // Ok dataconnection is down, now set the networktype Log.w(LOG_TAG, "Mobile Dataconnection is now down setting preferred NetworkType"); stopListening(); sendPreferedNetworktype( mDesiredNetworkType, obtainMessage(MESSAGE_SET_PREFERRED_NETWORK_TYPE)); mDesiredNetworkType = -1; } } } } }
/** * Creates a Call model from Call state and data received from the telephony layer. The telephony * layer maintains 3 conceptual objects: Phone, Call, Connection. * * <p>Phone represents the radio and there is an implementation per technology type such as * GSMPhone, SipPhone, CDMAPhone, etc. Generally, we will only ever deal with one instance of this * object for the lifetime of this class. * * <p>There are 3 Call instances that exist for the lifetime of this class which are created by * CallTracker. The three are RingingCall, ForegroundCall, and BackgroundCall. * * <p>A Connection most closely resembles what the layperson would consider a call. A Connection is * created when a user dials and it is "owned" by one of the three Call instances. Which of the * three Calls owns the Connection changes as the Connection goes between ACTIVE, HOLD, RINGING, and * other states. * * <p>This class models a new Call class from Connection objects received from the telephony layer. * We use Connection references as identifiers for a call; new reference = new call. * * <p>TODO: Create a new Call class to replace the simple call Id ints being used currently. * * <p>The new Call models are parcellable for transfer via the CallHandlerService API. */ public class CallModeler extends Handler { private static final String TAG = CallModeler.class.getSimpleName(); private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); private static final int CALL_ID_START_VALUE = 1; private final CallStateMonitor mCallStateMonitor; private final CallManager mCallManager; private final CallGatewayManager mCallGatewayManager; private final HashMap<Connection, Call> mCallMap = Maps.newHashMap(); private final HashMap<Connection, Call> mConfCallMap = Maps.newHashMap(); private final AtomicInteger mNextCallId = new AtomicInteger(CALL_ID_START_VALUE); private final ArrayList<Listener> mListeners = new ArrayList<Listener>(); private Connection mCdmaIncomingConnection; private Connection mCdmaOutgoingConnection; private boolean mNextGsmCallIsForwarded; private SuppServiceNotification mSuppSvcNotification; private boolean mVoicePrivacyState = false; public CallModeler( CallStateMonitor callStateMonitor, CallManager callManager, CallGatewayManager callGatewayManager) { mCallStateMonitor = callStateMonitor; mCallManager = callManager; mCallGatewayManager = callGatewayManager; mCallStateMonitor.addListener(this); } @Override public void handleMessage(Message msg) { switch (msg.what) { case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION: // We let the CallNotifier handle the new ringing connection first. When the custom // ringtone and send_to_voicemail settings are retrieved, CallNotifier will directly // call CallModeler's onNewRingingConnection. break; case CallStateMonitor.PHONE_DISCONNECT: onDisconnect((Connection) ((AsyncResult) msg.obj).result); break; case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED: // fall through case CallStateMonitor.PHONE_STATE_CHANGED: onPhoneStateChanged((AsyncResult) msg.obj); break; case CallStateMonitor.PHONE_ON_DIAL_CHARS: onPostDialChars((AsyncResult) msg.obj, (char) msg.arg1); break; case CallStateMonitor.PHONE_SUPP_SERVICE_NOTIFY: if (DBG) Log.d(TAG, "Received Supplementary Notification"); if (msg.obj != null) { onSuppServiceNotification((AsyncResult) msg.obj); } break; case CallStateMonitor.PHONE_ACTIVE_SUBSCRIPTION_CHANGE: onActiveSubChanged((AsyncResult) msg.obj); break; case CallStateMonitor.PHONE_SUPP_SERVICE_FAILED: AsyncResult r = (AsyncResult) msg.obj; Phone.SuppService service = (Phone.SuppService) r.result; int val = service.ordinal(); if (DBG) Log.d(TAG, "SUPP_SERVICE_FAILED..." + service); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onSuppServiceFailed(val); } break; case CallStateMonitor.PHONE_ENHANCED_VP_ON: if (DBG) Log.d(TAG, "PHONE_ENHANCED_VP_ON..."); if (!mVoicePrivacyState) { mVoicePrivacyState = true; onPhoneStateChanged(null); } break; case CallStateMonitor.PHONE_ENHANCED_VP_OFF: if (DBG) Log.d(TAG, "PHONE_ENHANCED_VP_OFF..."); if (mVoicePrivacyState) { mVoicePrivacyState = false; onPhoneStateChanged(null); } break; default: break; } } public void addListener(Listener listener) { Preconditions.checkNotNull(listener); Preconditions.checkNotNull(mListeners); if (!mListeners.contains(listener)) { mListeners.add(listener); } } public List<Call> getFullList() { final List<Call> calls = Lists.newArrayListWithCapacity(mCallMap.size() + mConfCallMap.size()); calls.addAll(mCallMap.values()); calls.addAll(mConfCallMap.values()); return calls; } public CallResult getCallWithId(int callId) { // max 8 connections, so this should be fast even through we are traversing the entire map. for (Entry<Connection, Call> entry : mCallMap.entrySet()) { if (entry.getValue().getCallId() == callId) { return new CallResult(entry.getValue(), entry.getKey()); } } for (Entry<Connection, Call> entry : mConfCallMap.entrySet()) { if (entry.getValue().getCallId() == callId) { return new CallResult(entry.getValue(), entry.getKey()); } } return null; } public boolean hasLiveCall() { return hasLiveCallInternal(mCallMap) || hasLiveCallInternal(mConfCallMap); } public void onCdmaCallWaiting(CdmaCallWaitingNotification callWaitingInfo) { // We dont get the traditional onIncomingCall notification for cdma call waiting, // but the Connection does actually exist. We need to find it in the set of ringing calls // and pass it through our normal incoming logic. final com.android.internal.telephony.Call teleCall = mCallManager.getFirstActiveRingingCall(); if (teleCall.getState() == com.android.internal.telephony.Call.State.WAITING) { Connection connection = teleCall.getLatestConnection(); if (connection != null) { String number = connection.getAddress(); if (number != null && number.equals(callWaitingInfo.number)) { Call call = onNewRingingConnection(connection); mCdmaIncomingConnection = connection; return; } } } Log.e(TAG, "CDMA Call waiting notification without a matching connection."); } public void onCdmaCallWaitingReject() { // Cdma call was rejected... if (mCdmaIncomingConnection != null) { onDisconnect(mCdmaIncomingConnection); mCdmaIncomingConnection = null; } else { Log.e(TAG, "CDMA Call waiting rejection without an incoming call."); } } /** * CDMA Calls have no sense of "dialing" state. For outgoing calls 3way calls we want to mimick * this state so that the the UI can notify the user that there is a "dialing" call. */ public void setCdmaOutgoing3WayCall(Connection connection) { boolean wasSet = mCdmaOutgoingConnection != null; mCdmaOutgoingConnection = connection; // If we reset the connection, that mean we can now tell the user that the call is actually // part of the conference call and move it out of the dialing state. To do this, issue a // new update completely. if (wasSet && mCdmaOutgoingConnection == null) { onPhoneStateChanged(null); } } private boolean hasLiveCallInternal(HashMap<Connection, Call> map) { for (Call call : map.values()) { final int state = call.getState(); if (state == Call.State.ACTIVE || state == Call.State.CALL_WAITING || state == Call.State.CONFERENCED || state == Call.State.DIALING || state == Call.State.REDIALING || state == Call.State.INCOMING || state == Call.State.ONHOLD || state == Call.State.DISCONNECTING) { return true; } } return false; } public boolean hasOutstandingActiveOrDialingCall() { return hasOutstandingActiveOrDialingCallInternal(mCallMap) || hasOutstandingActiveOrDialingCallInternal(mConfCallMap); } private static boolean hasOutstandingActiveOrDialingCallInternal(HashMap<Connection, Call> map) { for (Call call : map.values()) { final int state = call.getState(); if (state == Call.State.ACTIVE || Call.State.isDialing(state)) { return true; } } return false; } /** * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to * mPhone.setOnPostDialCharacter() above.) * * <p>TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go * directly to the Dialer to handle POST_ON_DIAL_CHARS too. */ private void onPostDialChars(AsyncResult r, char ch) { final Connection c = (Connection) r.result; if (c != null) { final Connection.PostDialState state = (Connection.PostDialState) r.userObj; switch (state) { case WAIT: final Call call = getCallFromMap(mCallMap, c, false); if (call == null) { Log.i(TAG, "Call no longer exists. Skipping onPostDialWait()."); } else { for (Listener mListener : mListeners) { mListener.onPostDialAction( state, call.getCallId(), c.getRemainingPostDialString(), ch); } } break; default: // This is primarily to cause the DTMFTonePlayer to play local tones. // Other listeners simply perform no-ops. for (Listener mListener : mListeners) { mListener.onPostDialAction(state, 0, "", ch); } break; } } } /* package */ Call onNewRingingConnection(Connection conn) { Log.i(TAG, "onNewRingingConnection"); final Call call = getCallFromMap(mCallMap, conn, true); if (call != null) { Phone phone = conn.getCall().getPhone(); if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM && mNextGsmCallIsForwarded) { call.setForwarded(true); mNextGsmCallIsForwarded = false; } updateCallFromConnection(call, conn, false); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onIncoming(call); } if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) { int subscription = conn.getCall().getPhone().getSubscription(); Log.i(TAG, "Setting Active sub : '" + subscription + "'"); PhoneUtils.setActiveSubscription(subscription); // if any local hold tones are playing then they need to be stoped. final MSimCallNotifier notifier = (MSimCallNotifier) PhoneGlobals.getInstance().notifier; notifier.manageLocalCallWaitingTone(); } } PhoneGlobals.getInstance().updateWakeState(); return call; } /* package */ void onUnsolCallModify(Connection conn) { final Call call = getCallFromMap(mCallMap, conn, false); copyDetails( conn.getCallModify().call_details, call.getCallModifyDetails(), conn.getCallModify().error + ""); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onModifyCall(call); } } private void onDisconnect(Connection conn) { Log.i(TAG, "onDisconnect"); mVoicePrivacyState = false; final Call call = getCallFromMap(mCallMap, conn, false); if (call != null) { final boolean wasConferenced = call.getState() == State.CONFERENCED; updateCallFromConnection(call, conn, false); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onDisconnect(call); } // If it was a conferenced call, we need to run the entire update // to make the proper changes to parent conference calls. if (wasConferenced) { onPhoneStateChanged(null); } mCallMap.remove(conn); } if (MSimTelephonyManager.getDefault().isMultiSimEnabled() && (call != null)) { mCallManager.clearDisconnected(call.getSubscription()); } else { mCallManager.clearDisconnected(); } PhoneGlobals.getInstance().updateWakeState(); } private void onSuppServiceNotification(AsyncResult r) { SuppServiceNotification notification = (SuppServiceNotification) r.result; Phone gsmPhone = PhoneUtils.getGsmPhone(mCallManager); Log.d(TAG, "SS Notification: " + notification); if (notification.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_MT) { if (notification.code == SuppServiceNotification.MT_CODE_FORWARDED_CALL || notification.code == SuppServiceNotification.MT_CODE_DEFLECTED_CALL) { com.android.internal.telephony.Call ringing = gsmPhone.getRingingCall(); if (ringing.getState().isRinging()) { final Call call = getCallForEarliestConnection(ringing); if (call != null) { call.setForwarded(true); notifyUpdateListeners(call); } } else { mNextGsmCallIsForwarded = true; } } else if (notification.code == SuppServiceNotification.MT_CODE_CALL_ON_HOLD || notification.code == SuppServiceNotification.MT_CODE_CALL_RETRIEVED) { final Call call = getCallForEarliestConnection(gsmPhone.getForegroundCall()); if (call != null) { boolean nowHeld = notification.code == SuppServiceNotification.MT_CODE_CALL_ON_HOLD; call.setHeldRemotely(nowHeld); notifyUpdateListeners(call); } } else if (notification.code == SuppServiceNotification.MT_CODE_ADDITIONAL_CALL_FORWARDED) { com.android.internal.telephony.Call fgCall = gsmPhone.getForegroundCall(); if (fgCall.getState().isAlive()) { final Call call = getCallForEarliestConnection(fgCall); if (call != null) { call.setAdditionalCallForwarded(true); notifyUpdateListeners(call); } } } } else if (notification.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_MO) { if (notification.code == SuppServiceNotification.MO_CODE_CALL_IS_WAITING) { com.android.internal.telephony.Call fgCall = gsmPhone.getForegroundCall(); if (fgCall.getState().isDialing()) { final Call call = getCallForEarliestConnection(fgCall); if (call != null) { call.setDialingIsWaiting(true); notifyUpdateListeners(call); } } } else if (notification.code == SuppServiceNotification.MO_CODE_INCOMING_CALLS_BARRED) { final Call call = getCallForEarliestConnection(gsmPhone.getForegroundCall()); if (call != null) { call.setRemoteIncomingCallBarringEnabled(true); notifyUpdateListeners(call); } } } } private Call getCallForEarliestConnection(com.android.internal.telephony.Call call) { return getCallFromMap(mCallMap, call.getEarliestConnection(), false); } private void notifyUpdateListeners(Call call) { final List<Call> updatedCalls = Lists.newArrayList(); updatedCalls.add(call); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onUpdate(updatedCalls); } } /** Called when the phone state changes. */ private void onPhoneStateChanged(AsyncResult r) { Log.i(TAG, "onPhoneStateChanged: "); // csvt state changed, do not update phone UI. if (PhoneGlobals.getInstance().isCsvtActive()) { Log.d(TAG, "csvt is active, do not update phone UI."); return; } final List<Call> updatedCalls = Lists.newArrayList(); doUpdate(false, updatedCalls); if (updatedCalls.size() > 0) { for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onUpdate(updatedCalls); } } PhoneGlobals.getInstance().updateWakeState(); } /** * Go through the Calls from CallManager and return the list of calls that were updated. Method * also finds any orphaned Calls (Connection objects no longer returned by telephony as either * ringing, foreground, or background). For each orphaned call, it sets the call state to IDLE and * adds it to the list of calls to update. * * @param fullUpdate Add all calls to out parameter including those that have no updates. * @param out List to populate with Calls that have been updated. */ private void doUpdate(boolean fullUpdate, List<Call> out) { final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList(); telephonyCalls.addAll(mCallManager.getRingingCalls()); telephonyCalls.addAll(mCallManager.getForegroundCalls()); telephonyCalls.addAll(mCallManager.getBackgroundCalls()); // orphanedConnections starts out including all connections we know about. // As we iterate through the connections we get from the telephony layer we // prune this Set down to only the connections we have but telephony no longer // recognizes. final Set<Connection> orphanedConnections = Sets.newHashSet(); orphanedConnections.addAll(mCallMap.keySet()); orphanedConnections.addAll(mConfCallMap.keySet()); // Cycle through all the Connections on all the Calls. Update our Call objects // to reflect any new state and send the updated Call objects to the handler service. for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) { for (Connection connection : telephonyCall.getConnections()) { if (DBG) Log.d(TAG, "connection: " + connection + connection.getState()); if (orphanedConnections.contains(connection)) { orphanedConnections.remove(connection); } // We only send updates for live calls which are not incoming (ringing). // Disconnected and incoming calls are handled by onDisconnect and // onNewRingingConnection. final boolean shouldUpdate = connection.getState() != com.android.internal.telephony.Call.State.DISCONNECTED && connection.getState() != com.android.internal.telephony.Call.State.IDLE && !connection.getState().isRinging(); final boolean isDisconnecting = connection.getState() == com.android.internal.telephony.Call.State.DISCONNECTING; // For disconnecting calls, we still need to send the update to the UI but we do // not create a new call if the call did not exist. final boolean shouldCreate = shouldUpdate && !isDisconnecting; // New connections return a Call with INVALID state, which does not translate to // a state in the internal.telephony.Call object. This ensures that staleness // check below fails and we always add the item to the update list if it is new. final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */); if (call == null || !shouldUpdate) { if (DBG) Log.d(TAG, "update skipped"); continue; } boolean changed = updateCallFromConnection(call, connection, false); if (fullUpdate || changed) { out.add(call); } } // We do a second loop to address conference call scenarios. We do this as a separate // loop to ensure all child calls are up to date before we start updating the parent // conference calls. for (Connection connection : telephonyCall.getConnections()) { updateForConferenceCalls(connection, out); } } // Iterate through orphaned connections, set them to idle, and remove // them from our internal structures. for (Connection orphanedConnection : orphanedConnections) { if (mCallMap.containsKey(orphanedConnection)) { final Call call = mCallMap.get(orphanedConnection); call.setState(Call.State.IDLE); out.add(call); mCallMap.remove(orphanedConnection); } if (mConfCallMap.containsKey(orphanedConnection)) { final Call call = mConfCallMap.get(orphanedConnection); call.setState(Call.State.IDLE); out.add(call); mConfCallMap.remove(orphanedConnection); } } } /** * Checks to see if the connection is the first connection in a conference call. If it is a * conference call, we will create a new Conference Call object or update the existing conference * call object for that connection. If it is not a conference call but a previous associated * conference call still exists, we mark it as idle and remove it from the map. In both cases * above, we add the Calls to be updated to the UI. * * @param connection The connection object to check. * @param updatedCalls List of 'updated' calls that will be sent to the UI. */ private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) { // We consider this connection a conference connection if the call it // belongs to is a multiparty call AND it is the first live connection. final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) && getEarliestLiveConnection(connection.getCall()) == connection; boolean changed = false; // If this connection is the main connection for the conference call, then create or update // a Call object for that conference call. if (isConferenceCallConnection) { final Call confCall = getCallFromMap(mConfCallMap, connection, true); changed = updateCallFromConnection(confCall, connection, true); if (changed) { updatedCalls.add(confCall); } if (DBG) Log.d(TAG, "Updating a conference call: " + confCall); // It is possible that through a conference call split, there may be lingering conference // calls where this connection was the main connection. We clean those up here. } else { final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false); // We found a conference call for this connection, which is no longer a conference call. // Kill it! if (oldConfCall != null) { if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall); mConfCallMap.remove(connection); oldConfCall.setState(State.IDLE); changed = true; // add to the list of calls to update updatedCalls.add(oldConfCall); } } return changed; } private Connection getEarliestLiveConnection(com.android.internal.telephony.Call call) { final List<Connection> connections = call.getConnections(); final int size = connections.size(); Connection earliestConn = null; long earliestTime = Long.MAX_VALUE; for (int i = 0; i < size; i++) { final Connection connection = connections.get(i); if (!connection.isAlive()) continue; final long time = connection.getCreateTime(); if (time < earliestTime) { earliestTime = time; earliestConn = connection; } } return earliestConn; } /** * Sets the new call state onto the call and performs some additional logic associated with * setting the state. */ private void setNewState(Call call, int newState, Connection connection) { Preconditions.checkState(call.getState() != newState); // When starting an outgoing call, we need to grab gateway information // for the call, if available, and set it. final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); if (Call.State.isDialing(newState)) { if (!info.isEmpty()) { call.setGatewayNumber(info.getFormattedGatewayNumber()); call.setGatewayPackage(info.packageName); } } else if (!Call.State.isConnected(newState)) { mCallGatewayManager.clearGatewayData(connection); } call.setState(newState); } private void mapCallDetails(Call call, Connection connection) { copyDetails(connection.getCallDetails(), call.getCallDetails(), connection.errorInfo); if (connection.getCallModify() != null) { copyDetails( connection.getCallModify().call_details, call.getCallModifyDetails(), connection.errorInfo); } if (connection.getCall().getConfUriList() != null) { String[] confList = connection.getCall().getConfUriList(); call.getCallDetails().setConfUriList(confList); } call.getCallDetails().setMpty(PhoneUtils.isConferenceCall(connection.getCall())); } /** * copy CallDetails of connection to CallDetails of Call * * @param src * @param dest * @param errorInfo */ private void copyDetails( com.android.internal.telephony.CallDetails src, com.android.services.telephony.common.CallDetails dest, String errorInfo) { dest.setCallType(src.call_type); dest.setCallDomain(src.call_domain); dest.setExtras(src.extras); dest.setErrorInfo(errorInfo); dest.setVideoPauseState(src.getVideoPauseState()); } /** * Updates the Call properties to match the state of the connection object that it represents. * * @param call The call object to update. * @param connection The connection object from which to update call. * @param isForConference There are slight differences in how we populate data for conference * calls. This boolean tells us which method to use. */ private boolean updateCallFromConnection( Call call, Connection connection, boolean isForConference) { boolean changed = false; final int newState = translateStateFromTelephony(connection, isForConference); if (call.getState() != newState) { setNewState(call, newState, connection); changed = true; } mapCallDetails(call, connection); final Call.DisconnectCause newDisconnectCause = translateDisconnectCauseFromTelephony(connection.getDisconnectCause()); if (call.getDisconnectCause() != newDisconnectCause) { call.setDisconnectCause(newDisconnectCause); changed = true; } final long oldConnectTime = call.getConnectTime(); if (oldConnectTime != connection.getConnectTime()) { call.setConnectTime(connection.getConnectTime()); changed = true; } // creation time should be fixed call.setCreateTime(connection.getCreateTime()); if (!isForConference) { // Number final String oldNumber = call.getNumber(); String newNumber = connection.getAddress(); RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); if (!info.isEmpty()) { newNumber = info.trueNumber; } if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) { call.setNumber(newNumber); changed = true; } // Number presentation final int newNumberPresentation = connection.getNumberPresentation(); if (call.getNumberPresentation() != newNumberPresentation) { call.setNumberPresentation(newNumberPresentation); changed = true; } // Name final String oldCnapName = call.getCnapName(); if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) { call.setCnapName(connection.getCnapName()); changed = true; } // Name Presentation final int newCnapNamePresentation = connection.getCnapNamePresentation(); if (call.getCnapNamePresentation() != newCnapNamePresentation) { call.setCnapNamePresentation(newCnapNamePresentation); changed = true; } } else { // update the list of children by: // 1) Saving the old set // 2) Removing all children // 3) Adding the correct children into the Call // 4) Comparing the new children set with the old children set ImmutableSortedSet<Integer> oldSet = call.getChildCallIds(); call.removeAllChildren(); if (connection.getCall() != null) { for (Connection childConn : connection.getCall().getConnections()) { final Call childCall = getCallFromMap(mCallMap, childConn, false); if (childCall != null && childConn.isAlive()) { call.addChildId(childCall.getCallId()); } } } changed |= !oldSet.equals(call.getChildCallIds()); } // Subscription id, this shall be done when Call object created. if (call.getSubscription() == MSimConstants.INVALID_SUBSCRIPTION) { call.setSubscription(connection.getCall().getPhone().getSubscription()); } /** !!! Uses values from connection and call collected above so this part must be last !!! */ final int newCapabilities = getCapabilitiesFor(connection, call, isForConference); if (call.getCapabilities() != newCapabilities) { call.setCapabilities(newCapabilities); changed = true; } return changed; } /** Returns a mask of capabilities for the connection such as merge, hold, etc. */ private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) { final boolean callIsActive = (call.getState() == Call.State.ACTIVE); final boolean callIsBackground = (call.getState() == Call.State.ONHOLD); final Phone phone = connection.getCall().getPhone(); boolean canAddCall = false; boolean canMergeCall = false; boolean canSwapCall = false; boolean canRespondViaText = false; boolean canMute = false; boolean canAddParticipant = false; boolean canModifyCall = false; boolean voicePrivacy = false; final boolean supportHold; final boolean canHold; final boolean genericConf = isForConference && (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA); if (!MSimTelephonyManager.getDefault().isMultiSimEnabled()) { supportHold = PhoneUtils.okToSupportHold(mCallManager); canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false); // only applies to active calls if (callIsActive) { canMergeCall = PhoneUtils.okToMergeCalls(mCallManager); canSwapCall = PhoneUtils.okToSwapCalls(mCallManager); } canAddCall = PhoneUtils.okToAddCall(mCallManager); } else { final int subscription = call.getSubscription(); supportHold = PhoneUtils.okToSupportHold(mCallManager, subscription); canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager, subscription) : false); // only applies to active calls if (callIsActive) { canMergeCall = PhoneUtils.okToMergeCalls(mCallManager, subscription); canSwapCall = PhoneUtils.okToSwapCalls(mCallManager, subscription); } canAddCall = PhoneUtils.okToAddCall(mCallManager, subscription); } if (callIsActive || callIsBackground) { canModifyCall = PhoneUtils.isVTModifyAllowed(connection); } canAddParticipant = PhoneUtils.canAddParticipant(mCallManager) && canAddCall; // "Mute": only enabled when the foreground call is ACTIVE. // (It's meaningless while on hold, or while DIALING/ALERTING.) // It's also explicitly disabled during emergency calls or if // emergency callback mode (ECM) is active. boolean isEmergencyCall = false; if (connection != null) { isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(), phone.getContext()); } boolean isECM = PhoneUtils.isPhoneInEcm(phone); if (isEmergencyCall || isECM) { // disable "Mute" item canMute = false; } else { canMute = callIsActive; } canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call, connection); // special rules section! // CDMA always has Add if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { canAddCall = true; } // Voice Privacy for CDMA if ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) && mVoicePrivacyState) { voicePrivacy = true; } int retval = 0x0; if (canHold) { retval |= Capabilities.HOLD; } if (supportHold) { retval |= Capabilities.SUPPORT_HOLD; } if (canAddCall) { retval |= Capabilities.ADD_CALL; } if (canMergeCall) { retval |= Capabilities.MERGE_CALLS; } if (canSwapCall) { retval |= Capabilities.SWAP_CALLS; } if (canRespondViaText) { retval |= Capabilities.RESPOND_VIA_TEXT; } if (canMute) { retval |= Capabilities.MUTE; } if (canAddParticipant) { retval |= Capabilities.ADD_PARTICIPANT; } if (genericConf) { retval |= Capabilities.GENERIC_CONFERENCE; } if (canModifyCall) { retval |= Capabilities.MODIFY_CALL; } if (voicePrivacy) { retval |= Capabilities.VOICE_PRIVACY; } return retval; } /** * Returns true if the Connection is part of a multiparty call. We do this by checking the * isMultiparty() method of the telephony.Call object and also checking to see if more than one of * it's children is alive. */ private boolean isPartOfLiveConferenceCall(Connection connection) { boolean ret = false; if (connection.getCall() != null && connection.getCall().isMultiparty()) { int count = 0; if (connection.getCallDetails().call_domain == com.android.services.telephony.common.CallDetails.CALL_DOMAIN_PS) { ret = true; } else { for (Connection currConn : connection.getCall().getConnections()) { // Only count connections which are alive and never cound // the special // "dialing" 3way call for CDMA calls. if (currConn.isAlive() && currConn != mCdmaOutgoingConnection) { count++; if (count >= 2) { return true; } } } } } return ret; } private int translateStateFromTelephony(Connection connection, boolean isForConference) { com.android.internal.telephony.Call.State connState = connection.getState(); // For the "fake" outgoing CDMA call, we need to always treat it as an outgoing call. if (mCdmaOutgoingConnection == connection) { connState = com.android.internal.telephony.Call.State.DIALING; } int retval = State.IDLE; switch (connState) { case ACTIVE: retval = State.ACTIVE; break; case INCOMING: retval = State.INCOMING; break; case DIALING: case ALERTING: if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) { retval = State.REDIALING; } else { retval = State.DIALING; } break; case WAITING: retval = State.CALL_WAITING; break; case HOLDING: retval = State.ONHOLD; break; case DISCONNECTING: retval = State.DISCONNECTING; break; case DISCONNECTED: retval = State.DISCONNECTED; default: } // If we are dealing with a potential child call (not the parent conference call), // the check to see if we have to set the state to CONFERENCED. if (!isForConference) { // if the connection is part of a multiparty call, and it is live, // annotate it with CONFERENCED state instead. if (isPartOfLiveConferenceCall(connection) && connection.isAlive()) { return State.CONFERENCED; } } return retval; } /** Called when the active subscription changes. */ private void onActiveSubChanged(AsyncResult r) { int activeSub = (Integer) r.result; Log.i(TAG, "onActiveSubChanged: " + activeSub); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onActiveSubChanged(activeSub); } } private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP = ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder() .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY) .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED) .put( Connection.DisconnectCause.CDMA_ACCESS_BLOCKED, Call.DisconnectCause.CDMA_ACCESS_BLOCKED) .put( Connection.DisconnectCause.CDMA_ACCESS_FAILURE, Call.DisconnectCause.CDMA_ACCESS_FAILURE) .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP) .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT) .put( Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE, Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE) .put( Connection.DisconnectCause.CDMA_NOT_EMERGENCY, Call.DisconnectCause.CDMA_NOT_EMERGENCY) .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED) .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER) .put(Connection.DisconnectCause.CDMA_RETRY_ORDER, Call.DisconnectCause.CDMA_RETRY_ORDER) .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT) .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION) .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED) .put( Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY, Call.DisconnectCause.CS_RESTRICTED_EMERGENCY) .put( Connection.DisconnectCause.CS_RESTRICTED_NORMAL, Call.DisconnectCause.CS_RESTRICTED_NORMAL) .put(Connection.DisconnectCause.ERROR_UNSPECIFIED, Call.DisconnectCause.ERROR_UNSPECIFIED) .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED) .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR) .put(Connection.DisconnectCause.INCOMING_MISSED, Call.DisconnectCause.INCOMING_MISSED) .put(Connection.DisconnectCause.INCOMING_REJECTED, Call.DisconnectCause.INCOMING_REJECTED) .put( Connection.DisconnectCause.INVALID_CREDENTIALS, Call.DisconnectCause.INVALID_CREDENTIALS) .put(Connection.DisconnectCause.INVALID_NUMBER, Call.DisconnectCause.INVALID_NUMBER) .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED) .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL) .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL) .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI) .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL) .put(Connection.DisconnectCause.NOT_DISCONNECTED, Call.DisconnectCause.NOT_DISCONNECTED) .put( Connection.DisconnectCause.NUMBER_UNREACHABLE, Call.DisconnectCause.NUMBER_UNREACHABLE) .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK) .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE) .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF) .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR) .put( Connection.DisconnectCause.SERVER_UNREACHABLE, Call.DisconnectCause.SERVER_UNREACHABLE) .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT) .put( Connection.DisconnectCause.UNOBTAINABLE_NUMBER, Call.DisconnectCause.UNOBTAINABLE_NUMBER) .put( Connection.DisconnectCause.DIAL_MODIFIED_TO_USSD, Call.DisconnectCause.DIAL_MODIFIED_TO_USSD) .put( Connection.DisconnectCause.DIAL_MODIFIED_TO_SS, Call.DisconnectCause.DIAL_MODIFIED_TO_SS) .put( Connection.DisconnectCause.DIAL_MODIFIED_TO_DIAL, Call.DisconnectCause.DIAL_MODIFIED_TO_DIAL) .put(Connection.DisconnectCause.SRVCC_CALL_DROP, Call.DisconnectCause.SRVCC_CALL_DROP) .put( Connection.DisconnectCause.ANSWERED_ELSEWHERE, Call.DisconnectCause.ANSWERED_ELSEWHERE) .put(Connection.DisconnectCause.CALL_FAIL_MISC, Call.DisconnectCause.CALL_FAIL_MISC) .build(); private Call.DisconnectCause translateDisconnectCauseFromTelephony( Connection.DisconnectCause causeSource) { if (CAUSE_MAP.containsKey(causeSource)) { return CAUSE_MAP.get(causeSource); } return Call.DisconnectCause.UNKNOWN; } /** * Gets an existing callId for a connection, or creates one if none exists. This function does NOT * set any of the Connection data onto the Call class. A separate call to updateCallFromConnection * must be made for that purpose. */ private Call getCallFromMap( HashMap<Connection, Call> map, Connection conn, boolean createIfMissing) { Call call = null; // Find the call id or create if missing and requested. if (conn != null) { if (map.containsKey(conn)) { call = map.get(conn); } else if (createIfMissing) { call = createNewCall(); map.put(conn, call); } } return call; } /** Creates a brand new connection for the call. */ private Call createNewCall() { int callId; int newNextCallId; do { callId = mNextCallId.get(); // protect against overflow newNextCallId = (callId == Integer.MAX_VALUE ? CALL_ID_START_VALUE : callId + 1); // Keep looping if the change was not atomic OR the value is already taken. // The call to containsValue() is linear, however, most devices support a // maximum of 7 connections so it's not expensive. } while (!mNextCallId.compareAndSet(callId, newNextCallId)); return new Call(callId); } /** Listener interface for changes to Calls. */ public interface Listener { void onDisconnect(Call call); void onIncoming(Call call); void onUpdate(List<Call> calls); void onPostDialAction( Connection.PostDialState state, int callId, String remainingChars, char c); void onActiveSubChanged(int activeSub); void onModifyCall(Call call); void onSuppServiceFailed(int service); } /** Result class for accessing a call by connection. */ public static class CallResult { public Call mCall; public Call mActionableCall; public Connection mConnection; private CallResult(Call call, Connection connection) { this(call, call, connection); } private CallResult(Call call, Call actionableCall, Connection connection) { mCall = call; mActionableCall = actionableCall; mConnection = connection; } public Call getCall() { return mCall; } // The call that should be used for call actions like hanging up. public Call getActionableCall() { return mActionableCall; } public Connection getConnection() { return mConnection; } } }
public GLTextureView(Context context, AttributeSet attrs) { super(context, attrs); setSurfaceTextureListener(this); int debugOptions = SystemProperties.getInt("debug.ngin3d.enable", 0); mShowFPS = ((Ngin3d.DEBUG_SHOW_FPS & debugOptions) != 0); }
/** * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and broadcasts the * ACTION_NEW_OUTGOING_CALL intent which allows other applications to monitor, redirect, or prevent * the outgoing call. * * <p>After the other applications have had a chance to see the ACTION_NEW_OUTGOING_CALL intent, it * finally reaches the {@link OutgoingCallReceiver}, which passes the (possibly modified) intent on * to the {@link SipCallOptionHandler}, which will ultimately start the call using the * CallController.placeCall() API. * * <p>Emergency calls and calls where no number is present (like for a CDMA "empty flash" or a * nonexistent voicemail number) are exempt from being broadcast. */ public class OutgoingCallBroadcaster extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS; private static final String TAG = "OutgoingCallBroadcaster"; private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); // Do not check in with VDBG = true, since that may write PII to the system log. private static final boolean VDBG = false; private int mSubscription; public static final String ACTION_SIP_SELECT_PHONE = "com.android.phone.SIP_SELECT_PHONE"; public static final String EXTRA_ALREADY_CALLED = "android.phone.extra.ALREADY_CALLED"; public static final String EXTRA_ORIGINAL_URI = "android.phone.extra.ORIGINAL_URI"; public static final String EXTRA_NEW_CALL_INTENT = "android.phone.extra.NEW_CALL_INTENT"; public static final String EXTRA_SIP_PHONE_URI = "android.phone.extra.SIP_PHONE_URI"; public static final String EXTRA_ACTUAL_NUMBER_TO_DIAL = "android.phone.extra.ACTUAL_NUMBER_TO_DIAL"; /** the key used to specify subscription to be used for emergency calls */ public static final String BLUETOOTH = "Bluetooth"; /** * Identifier for intent extra for sending an empty Flash message for CDMA networks. This message * is used by the network to simulate a press/depress of the "hookswitch" of a landline phone. Aka * "empty flash". * * <p>TODO: Receiving an intent extra to tell the phone to send this flash is a temporary measure. * To be replaced with an external ITelephony call in the future. TODO: Keep in sync with the * string defined in TwelveKeyDialer.java in Contacts app until this is replaced with the * ITelephony API. */ public static final String EXTRA_SEND_EMPTY_FLASH = "com.android.phone.extra.SEND_EMPTY_FLASH"; // Dialog IDs private static final int DIALOG_NOT_VOICE_CAPABLE = 1; /** * OutgoingCallReceiver finishes NEW_OUTGOING_CALL broadcasts, starting the InCallScreen if the * broadcast has not been canceled, possibly with a modified phone number and optional provider * info (uri + package name + remote views.) */ public class OutgoingCallReceiver extends BroadcastReceiver { private static final String TAG = "OutgoingCallReceiver"; public void onReceive(Context context, Intent intent) { doReceive(context, intent); finish(); } public void doReceive(Context context, Intent intent) { if (DBG) Log.v(TAG, "doReceive: " + intent); boolean alreadyCalled; String number; String originalUri; alreadyCalled = intent.getBooleanExtra(OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false); if (alreadyCalled) { if (DBG) Log.v(TAG, "CALL already placed -- returning."); return; } // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData // is used as the actual number to call. (If null, no call will be // placed.) number = getResultData(); if (VDBG) Log.v(TAG, "- got number from resultData: '" + number + "'"); final PhoneApp app = PhoneApp.getInstance(); if (isOtaActive()) { // OTASP call is active. Don't allow new outgoing calls at all Log.w(TAG, "OTASP call is active: disallowing a new outgoing call."); return; } if (number == null) { if (DBG) Log.v(TAG, "CALL cancelled (null number), returning..."); return; } else if (TelephonyCapabilities.supportsOtasp(app.phone) && (app.phone.getState() != Phone.State.IDLE) && (app.phone.isOtaSpNumber(number))) { if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning."); return; } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, context)) { // Just like 3rd-party apps aren't allowed to place emergency // calls via the ACTION_CALL intent, we also don't allow 3rd // party apps to use the NEW_OUTGOING_CALL broadcast to rewrite // an outgoing call into an emergency number. Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + "."); return; } originalUri = intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI); if (originalUri == null) { Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning."); return; } Uri uri = Uri.parse(originalUri); // We already called convertKeypadLettersToDigits() and // stripSeparators() way back in onCreate(), before we sent out the // NEW_OUTGOING_CALL broadcast. But we need to do it again here // too, since the number might have been modified/rewritten during // the broadcast (and may now contain letters or separators again.) number = PhoneNumberUtils.convertKeypadLettersToDigits(number); number = PhoneNumberUtils.stripSeparators(number); if (DBG) Log.v(TAG, "doReceive: proceeding with call..."); if (VDBG) Log.v(TAG, "- uri: " + uri); if (VDBG) Log.v(TAG, "- actual number to dial: '" + number + "'"); startSipCallOptionHandler(context, intent, uri, number); } } /** * cleanup any undismissed ota dialogs so the InCallScreen UI can be shown * * @return void */ private void otaCleanup() { PhoneApp app = PhoneApp.getInstance(); boolean isOtaCallActive = false; if (TelephonyCapabilities.supportsOtasp(app.phone)) { boolean activateState = (app.cdmaOtaScreenState.otaScreenState == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION); boolean dialogState = (app.cdmaOtaScreenState.otaScreenState == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG); if (activateState || dialogState) { // The OTASP sequence is active, but either (1) the call // hasn't started yet, or (2) the call has ended and we're // showing the success/failure screen. In either of these // cases it's OK to make a new outgoing call, but we need // to take down any OTASP-related UI first. if (dialogState) app.dismissOtaDialogs(); app.clearOtaState(); app.clearInCallScreenMode(); } } } /** * Check if ota call is active * * @return True if ota call is still active False if ota call is not active */ private boolean isOtaActive() { PhoneApp app = PhoneApp.getInstance(); boolean isOtaCallActive = false; if (TelephonyCapabilities.supportsOtasp(app.phone)) { // TODO: Need cleaner way to check if OTA is active. // Also, this check seems to be broken in one obscure case: if // you interrupt an OTASP call by pressing Back then Skip, // otaScreenState somehow gets left in either PROGRESS or // LISTENING. if ((app.cdmaOtaScreenState.otaScreenState == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS) || (app.cdmaOtaScreenState.otaScreenState == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) { isOtaCallActive = true; // The actual OTASP call is active. Don't allow new // outgoing calls at all from this state. Log.w(TAG, "OTASP call is active"); } } return isOtaCallActive; } /** * Launch the SipCallOptionHandler, which is the next step(*) in the outgoing-call sequence after * the outgoing call broadcast is complete. * * <p>(*) We now know exactly what phone number we need to dial, so the next step is for the * SipCallOptionHandler to decide which Phone type (SIP or PSTN) should be used. (Depending on the * user's preferences, this decision may also involve popping up a dialog to ask the user to * choose what type of call this should be.) * * @param context used for the startActivity() call * @param intent the intent from the previous step of the outgoing-call sequence. Normally this * will be the NEW_OUTGOING_CALL broadcast intent that came in to the OutgoingCallReceiver, * although it can also be the original ACTION_CALL intent that started the whole sequence (in * cases where we don't do the NEW_OUTGOING_CALL broadcast at all, like for emergency numbers * or SIP addresses). * @param uri the data URI from the original CALL intent, presumably either a tel: or sip: URI. * For tel: URIs, note that the scheme-specific part does *not* necessarily have separators * and keypad letters stripped (so we might see URIs like "tel:(650)%20555-1234" or * "tel:1-800-GOOG-411" here.) * @param number the actual number (or SIP address) to dial. This is guaranteed to be either a * PSTN phone number with separators stripped out and keypad letters converted to digits (like * "16505551234"), or a raw SIP address (like "*****@*****.**"). */ private void startSipCallOptionHandler(Context context, Intent intent, Uri uri, String number) { if (VDBG) { Log.i(TAG, "startSipCallOptionHandler..."); Log.i(TAG, "- intent: " + intent); Log.i(TAG, "- uri: " + uri); Log.i(TAG, "- number: " + number); } // Create a copy of the original CALL intent that started the whole // outgoing-call sequence. This intent will ultimately be passed to // CallController.placeCall() after the SipCallOptionHandler step. Intent newIntent = new Intent(Intent.ACTION_CALL, uri); newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number); newIntent.putExtra(SUBSCRIPTION_KEY, mSubscription); PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent); // Finally, launch the SipCallOptionHandler, with the copy of the // original CALL intent stashed away in the EXTRA_NEW_CALL_INTENT // extra. Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri); selectPhoneIntent.setClass(context, SipCallOptionHandler.class); selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent); selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (DBG) Log.v(TAG, "startSipCallOptionHandler(): " + "calling startActivity: " + selectPhoneIntent); context.startActivity(selectPhoneIntent); // ...and see SipCallOptionHandler.onCreate() for the next step of the sequence. } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); // This method is the single point of entry for the CALL intent, // which is used (by built-in apps like Contacts / Dialer, as well // as 3rd-party apps) to initiate an outgoing voice call. // // We also handle two related intents which are only used internally: // CALL_PRIVILEGED (which can come from built-in apps like contacts / // voice dialer / bluetooth), and CALL_EMERGENCY (from the // EmergencyDialer that's reachable from the lockscreen.) // // The exact behavior depends on the intent's data: // // - The most typical is a tel: URI, which we handle by starting the // NEW_OUTGOING_CALL broadcast. That broadcast eventually triggeres // the sequence OutgoingCallReceiver -> SipCallOptionHandler -> // InCallScreen. // // - Or, with a sip: URI we skip the NEW_OUTGOING_CALL broadcast and // go directly to SipCallOptionHandler, which then leads to the // InCallScreen. // // - voicemail: URIs take the same path as regular tel: URIs. // // Other special cases: // // - Outgoing calls are totally disallowed on non-voice-capable // devices (see handleNonVoiceCapable()). // // - A CALL intent with the EXTRA_SEND_EMPTY_FLASH extra (and // presumably no data at all) means "send an empty flash" (which // is only meaningful on CDMA devices while a call is already // active.) Intent intent = getIntent(); final Configuration configuration = getResources().getConfiguration(); if (DBG) Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle); if (DBG) Log.v(TAG, " - getIntent() = " + intent); if (DBG) Log.v(TAG, " - configuration = " + configuration); if (icicle != null) { // A non-null icicle means that this activity is being // re-initialized after previously being shut down. // // In practice this happens very rarely (because the lifetime // of this activity is so short!), but it *can* happen if the // framework detects a configuration change at exactly the // right moment; see bug 2202413. // // In this case, do nothing. Our onCreate() method has already // run once (with icicle==null the first time), which means // that the NEW_OUTGOING_CALL broadcast for this new call has // already been sent. Log.i( TAG, "onCreate: non-null icicle! " + "Bailing out, not sending NEW_OUTGOING_CALL broadcast..."); // No need to finish() here, since the OutgoingCallReceiver from // our original instance will do that. (It'll actually call // finish() on our original instance, which apparently works fine // even though the ActivityManager has already shut that instance // down. And note that if we *do* call finish() here, that just // results in an "ActivityManager: Duplicate finish request" // warning when the OutgoingCallReceiver runs.) return; } // Outgoing phone calls are only allowed on "voice-capable" devices. if (!PhoneApp.sVoiceCapable) { handleNonVoiceCapable(intent); // No need to finish() here; handleNonVoiceCapable() will do // that if necessary. return; } /* * Clean up any undismissed ota dialogs. If ota call is active outgoing * calls will be blocked in OutgoingCallReceiver */ otaCleanup(); boolean promptEnabled = MSimPhoneFactory.isPromptEnabled(); String number = PhoneNumberUtils.getNumberFromIntent(intent, this); if (TelephonyManager.getDefault().isMultiSimEnabled() && promptEnabled && (activeSubCount() > 1) && (!isIntentFromBluetooth(intent)) && (!isSIPCall(number, intent))) { Log.d(TAG, "Start multisimdialer activity and get the sub selected by user"); Intent intentMSim = new Intent(this, MSimDialerActivity.class); intentMSim.setData(intent.getData()); intentMSim.setAction(intent.getAction()); int requestCode = 1; startActivityForResult(intentMSim, requestCode); } else { mSubscription = intent.getIntExtra(SUBSCRIPTION_KEY, PhoneApp.getInstance().getVoiceSubscription()); Log.d(TAG, "subscription when there is (from Extra):" + mSubscription); processIntent(intent); } } private void processIntent(Intent intent) { String action = intent.getAction(); intent.putExtra(SUBSCRIPTION_KEY, mSubscription); Log.d(TAG, "outGoingcallBroadCaster action is" + action); String number = PhoneNumberUtils.getNumberFromIntent(intent, this); Log.d(TAG, " number from Intent : " + number); // Check the number, don't convert for sip uri // TODO put uriNumber under PhoneNumberUtils if (number != null) { if (!PhoneNumberUtils.isUriNumber(number)) { number = PhoneNumberUtils.convertKeypadLettersToDigits(number); number = PhoneNumberUtils.stripSeparators(number); } } // If true, this flag will indicate that the current call is a special kind // of call (most likely an emergency number) that 3rd parties aren't allowed // to intercept or affect in any way. (In that case, we start the call // immediately rather than going through the NEW_OUTGOING_CALL sequence.) boolean callNow; if (getClass().getName().equals(intent.getComponent().getClassName())) { // If we were launched directly from the OutgoingCallBroadcaster, // not one of its more privileged aliases, then make sure that // only the non-privileged actions are allowed. if (!Intent.ACTION_CALL.equals(intent.getAction())) { Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL"); intent.setAction(Intent.ACTION_CALL); } } // Check whether or not this is an emergency number, in order to // enforce the restriction that only the CALL_PRIVILEGED and // CALL_EMERGENCY intents are allowed to make emergency calls. // // (Note that the ACTION_CALL check below depends on the result of // isPotentialLocalEmergencyNumber() rather than just plain // isLocalEmergencyNumber(), to be 100% certain that we *don't* // allow 3rd party apps to make emergency calls by passing in an // "invalid" number like "9111234" that isn't technically an // emergency number but might still result in an emergency call // with some networks.) final boolean isExactEmergencyNumber = (number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this); final boolean isPotentialEmergencyNumber = (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this); if (VDBG) { Log.v(TAG, "- Checking restrictions for number '" + number + "':"); Log.v(TAG, " isExactEmergencyNumber = " + isExactEmergencyNumber); Log.v(TAG, " isPotentialEmergencyNumber = " + isPotentialEmergencyNumber); } /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */ // TODO: This code is redundant with some code in InCallScreen: refactor. if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) { // We're handling a CALL_PRIVILEGED intent, so we know this request came // from a trusted source (like the built-in dialer.) So even a number // that's *potentially* an emergency number can safely be promoted to // CALL_EMERGENCY (since we *should* allow you to dial "91112345" from // the dialer if you really want to.) action = isPotentialEmergencyNumber ? Intent.ACTION_CALL_EMERGENCY : Intent.ACTION_CALL; if (DBG) Log.v(TAG, "- updating action from CALL_PRIVILEGED to " + action); intent.setAction(action); } if (Intent.ACTION_CALL.equals(action)) { if (isPotentialEmergencyNumber) { Log.w( TAG, "Cannot call potential emergency number '" + number + "' with CALL Intent " + intent + "."); Log.i(TAG, "Launching default dialer instead..."); Intent invokeFrameworkDialer = new Intent(); // TwelveKeyDialer is in a tab so we really want // DialtactsActivity. Build the intent 'manually' to // use the java resolver to find the dialer class (as // opposed to a Context which look up known android // packages only) invokeFrameworkDialer.setClassName( "com.android.contacts", "com.android.contacts.DialtactsActivity"); invokeFrameworkDialer.setAction(Intent.ACTION_DIAL); invokeFrameworkDialer.setData(intent.getData()); invokeFrameworkDialer.putExtra(SUBSCRIPTION_KEY, mSubscription); if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: " + invokeFrameworkDialer); startActivity(invokeFrameworkDialer); finish(); return; } intent.putExtra(SUBSCRIPTION_KEY, mSubscription); Log.d(TAG, "for non emergency call,sub is :" + mSubscription); callNow = false; } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) { // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED // intent that we just turned into a CALL_EMERGENCY intent (see // above), or else it really is an CALL_EMERGENCY intent that // came directly from some other app (e.g. the EmergencyDialer // activity built in to the Phone app.) // Make sure it's at least *possible* that this is really an // emergency number. if (!isPotentialEmergencyNumber) { Log.w( TAG, "Cannot call non-potential-emergency number " + number + " with EMERGENCY_CALL Intent " + intent + "."); finish(); return; } int sub = PhoneApp.getInstance().getVoiceSubscriptionInService(); intent.putExtra(SUBSCRIPTION_KEY, sub); Log.d(TAG, "Attempting emergency call on sub :" + sub); callNow = true; } else { Log.e(TAG, "Unhandled Intent " + intent + "."); finish(); return; } // Make sure the screen is turned on. This is probably the right // thing to do, and more importantly it works around an issue in the // activity manager where we will not launch activities consistently // when the screen is off (since it is trying to keep them paused // and has... issues). // // Also, this ensures the device stays awake while doing the following // broadcast; technically we should be holding a wake lock here // as well. PhoneApp.getInstance().wakeUpScreen(); /* If number is null, we're probably trying to call a non-existent voicemail number, * send an empty flash or something else is fishy. Whatever the problem, there's no * number, so there's no point in allowing apps to modify the number. */ if (number == null || TextUtils.isEmpty(number)) { if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) { Log.i(TAG, "onCreate: SEND_EMPTY_FLASH..."); PhoneUtils.sendEmptyFlash(PhoneApp.getInstance().getPhone()); finish(); return; } else { Log.i(TAG, "onCreate: null or empty number, setting callNow=true..."); callNow = true; intent.putExtra(SUBSCRIPTION_KEY, mSubscription); } } if (callNow) { // This is a special kind of call (most likely an emergency number) // that 3rd parties aren't allowed to intercept or affect in any way. // So initiate the outgoing call immediately. if (DBG) Log.v(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent); // Initiate the outgoing call, and simultaneously launch the // InCallScreen to display the in-call UI: PhoneApp.getInstance().callController.placeCall(intent); // Note we do *not* "return" here, but instead continue and // send the ACTION_NEW_OUTGOING_CALL broadcast like for any // other outgoing call. (But when the broadcast finally // reaches the OutgoingCallReceiver, we'll know not to // initiate the call again because of the presence of the // EXTRA_ALREADY_CALLED extra.) } // For now, SIP calls will be processed directly without a // NEW_OUTGOING_CALL broadcast. // // TODO: In the future, though, 3rd party apps *should* be allowed to // intercept outgoing calls to SIP addresses as well. To do this, we should // (1) update the NEW_OUTGOING_CALL intent documentation to explain this // case, and (2) pass the outgoing SIP address by *not* overloading the // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold // the outgoing SIP address. (Be sure to document whether it's a URI or just // a plain address, whether it could be a tel: URI, etc.) Uri uri = intent.getData(); String scheme = uri.getScheme(); if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) { startSipCallOptionHandler(this, intent, uri, number); finish(); return; // TODO: if there's ever a way for SIP calls to trigger a // "callNow=true" case (see above), we'll need to handle that // case here too (most likely by just doing nothing at all.) } final String callOrigin = intent.getStringExtra(PhoneApp.EXTRA_CALL_ORIGIN); if (callOrigin != null) { if (DBG) Log.v(TAG, "Call origin is passed (" + callOrigin + ")"); PhoneApp.getInstance().setLatestActiveCallOrigin(callOrigin); } else { if (DBG) Log.v(TAG, "Call origin is not passed. Reset current one."); PhoneApp.getInstance().setLatestActiveCallOrigin(null); } Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); if (number != null) { broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); } PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent); broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow); broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString()); broadcastIntent.putExtra(SUBSCRIPTION_KEY, mSubscription); if (DBG) Log.v(TAG, "Broadcasting intent: " + broadcastIntent + "."); sendOrderedBroadcast( broadcastIntent, PERMISSION, new OutgoingCallReceiver(), null, // scheduler Activity.RESULT_OK, // initialCode number, // initialData: initial value for the result data null); // initialExtras } @Override protected void onStop() { // Clean up (and dismiss if necessary) any managed dialogs. // // We don't do this in onPause() since we can be paused/resumed // due to orientation changes (in which case we don't want to // disturb the dialog), but we *do* need it here in onStop() to be // sure we clean up if the user hits HOME while the dialog is up. // // Note it's safe to call removeDialog() even if there's no dialog // associated with that ID. removeDialog(DIALOG_NOT_VOICE_CAPABLE); super.onStop(); } /** * Handle the specified CALL or CALL_* intent on a non-voice-capable device. * * <p>This method may launch a different intent (if there's some useful alternative action to * take), or otherwise display an error dialog, and in either case will finish() the current * activity when done. */ private void handleNonVoiceCapable(Intent intent) { if (DBG) Log.v(TAG, "handleNonVoiceCapable: handling " + intent + " on non-voice-capable device..."); String action = intent.getAction(); Uri uri = intent.getData(); String scheme = uri.getScheme(); // Handle one special case: If this is a regular CALL to a tel: URI, // bring up a UI letting you do something useful with the phone number // (like "Add to contacts" if it isn't a contact yet.) // // This UI is provided by the contacts app in response to a DIAL // intent, so we bring it up here by demoting this CALL to a DIAL and // relaunching. // // TODO: it's strange and unintuitive to manually launch a DIAL intent // to do this; it would be cleaner to have some shared UI component // that we could bring up directly. (But for now at least, since both // Contacts and Phone are built-in apps, this implementation is fine.) if (Intent.ACTION_CALL.equals(action) && (Constants.SCHEME_TEL.equals(scheme))) { Intent newIntent = new Intent(Intent.ACTION_DIAL, uri); if (DBG) Log.v(TAG, "- relaunching as a DIAL intent: " + newIntent); startActivity(newIntent); finish(); return; } // In all other cases, just show a generic "voice calling not // supported" dialog. showDialog(DIALOG_NOT_VOICE_CAPABLE); // ...and we'll eventually finish() when the user dismisses // or cancels the dialog. } @Override protected Dialog onCreateDialog(int id) { Dialog dialog; switch (id) { case DIALOG_NOT_VOICE_CAPABLE: dialog = new AlertDialog.Builder(this) .setTitle(R.string.not_voice_capable) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.ok, this) .setOnCancelListener(this) .create(); break; default: Log.w(TAG, "onCreateDialog: unexpected ID " + id); dialog = null; break; } return dialog; } // DialogInterface.OnClickListener implementation public void onClick(DialogInterface dialog, int id) { // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far // at least), and its only button is "OK". finish(); } // DialogInterface.OnCancelListener implementation public void onCancel(DialogInterface dialog) { // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far // at least), and canceling it is just like hitting "OK". finish(); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Collect subscription data from the intent and use it if (resultCode == RESULT_CANCELED) { Log.d(TAG, "activity cancelled or backkey pressed "); finish(); } else if (resultCode == RESULT_OK) { Bundle extras = data.getExtras(); mSubscription = extras.getInt(SUBSCRIPTION_KEY); Log.d(TAG, "subscription selected from multiSimDialer" + mSubscription); processIntent(data); } } private int activeSubCount() { SubscriptionManager subManager = SubscriptionManager.getInstance(); int count = subManager.getActiveSubscriptionsCount(); if (DBG) Log.v(TAG, "count of subs activated " + count); return count; } private boolean isIntentFromBluetooth(Intent intent) { boolean btIntent = false; Bundle extras = intent.getExtras(); if (extras != null) { if ((extras.getString(BLUETOOTH) != null) && (extras.getString(BLUETOOTH).equals("true"))) { btIntent = true; Log.d(TAG, "isIntentFromBluetooth " + btIntent + "intent :" + extras.getString(BLUETOOTH)); } } return btIntent; } private boolean isSIPCall(String number, Intent intent) { boolean sipCall = false; String scheme = ""; if (intent.getData() != null) { scheme = intent.getData().getScheme(); if ((scheme != null) && ("sip".equals(scheme) || PhoneNumberUtils.isUriNumber(number))) { sipCall = true; } } Log.d(TAG, "isSIPCall : " + sipCall); return sipCall; } // Implement onConfigurationChanged() purely for debugging purposes, // to make sure that the android:configChanges element in our manifest // is working properly. @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig); } }
/** * Determines whether a query should be logged. * * <p>Reads the "db.log.slow_query_threshold" system property, which can be changed by the user at * any time. If the value is zero, then all queries will be considered slow. If the value does not * exist, then no queries will be considered slow. * * <p>This value can be changed dynamically while the system is running. * * @hide */ public static final boolean shouldLogSlowQuery(long elapsedTimeMillis) { int slowQueryMillis = SystemProperties.getInt("db.log.slow_query_threshold", -1); return slowQueryMillis >= 0 && elapsedTimeMillis > slowQueryMillis; }