@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;
  }
Beispiel #11
0
/** 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();
  }
}
Beispiel #13
0
/** 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");
      }
    }
  }
Beispiel #24
0
/** 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;
        }
      }
    }
  }
}
Beispiel #26
0
/**
 * 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);
  }
}
Beispiel #29
0
 /**
  * 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;
 }