public void addCallMonitor(TsapiCallMonitor obs)
			throws TsapiResourceUnavailableException {
		if (this.provider.getCapabilities().getMonitorCall() == 0) {
			throw new TsapiResourceUnavailableException(0, 0, 0,
					"unsupported by driver");
		}

		if (!this.provider.allowCallMonitoring()) {
			throw new TsapiResourceUnavailableException(0, 0, 0,
					"no permissions");
		}

		switch (this.state) {
		case 32:
			if (this.monitorThreads.contains(obs)) {
				return;
			}

			synchronized (this) {
				this.monitorPending = true;
				this.callbackAndTypeVector.addElement(new CallbackAndType(obs,
						null));
			}
			break;
		case 34:
			if (this.monitorThreads.contains(obs)) {
				return;
			}

			obs.addReference();
			obs.deleteReference(this, false, 100, null);
			break;
		default:
			synchronized (this.obsSync) {
				if (this.monitorThreads.contains(obs)) {
					return;
				}

				boolean foundDevObs = false;

				synchronized (this.monitorThreads) {
					synchronized (this.deviceObsVector) {
						for (int i = 0; i < this.deviceObsVector.size(); i++) {
							if (((DeviceObs) this.deviceObsVector.elementAt(i)).callback == obs) {
								foundDevObs = true;

								this.monitorThreads
										.addElement(((DeviceObs) this.deviceObsVector
												.elementAt(i)).callback);
								this.deviceObsVector.removeElementAt(i);

								break;
							}

						}

					}

				}

				if (foundDevObs) {
					setMonitor(true);
					return;
				}

				if (this.staleObsVector.removeElement(obs)) {
					setMonitor(true);

					this.monitorThreads.addElement(obs);

					return;
				}

				setMonitor(true);

				this.monitorThreads.addElement(obs);
			}

			obs.addReference();

			sendSnapshot(obs, null, true);
		}
	}