public AndroidLayoutPreviewToolWindowManager(
      final Project project, final FileEditorManager fileEditorManager) {
    myProject = project;
    myFileEditorManager = fileEditorManager;

    myToolWindowUpdateQueue =
        new MergingUpdateQueue("android.layout.preview", 100, true, null, project);

    final MessageBusConnection connection = project.getMessageBus().connect(project);
    connection.subscribe(
        FileEditorManagerListener.FILE_EDITOR_MANAGER, new MyFileEditorManagerListener());
    connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyAndroidPlatformListener(project));

    PsiManager.getInstance(project)
        .addPsiTreeChangeListener(
            new PsiTreeChangeAdapter() {
              boolean myIgnoreChildrenChanged;

              @Override
              public void beforeChildrenChange(@NotNull PsiTreeChangeEvent event) {
                myIgnoreChildrenChanged = false;
              }

              @Override
              public void childrenChanged(@NotNull PsiTreeChangeEvent event) {
                // See ResourceFolderManager#PsiListener#childrenChanged
                if (isRelevant(event)
                    && !myIgnoreChildrenChanged
                    && event.getParent() != event.getChild()) {
                  update(event);
                }
              }

              @Override
              public void childAdded(@NotNull PsiTreeChangeEvent event) {
                myIgnoreChildrenChanged = true;
                if (isRelevant(event)) {
                  PsiElement child = event.getChild();
                  PsiElement parent = event.getParent();

                  if (child instanceof XmlAttribute && parent instanceof XmlTag) {
                    // Typing in a new attribute. Don't need to do any rendering until there
                    // is an actual value
                    if (((XmlAttribute) child).getValueElement() == null) {
                      return;
                    }
                  } else if (parent instanceof XmlAttribute && child instanceof XmlAttributeValue) {
                    XmlAttributeValue attributeValue = (XmlAttributeValue) child;
                    if (attributeValue.getValue() == null || attributeValue.getValue().isEmpty()) {
                      // Just added a new blank attribute; nothing to render yet
                      return;
                    }
                  } else if (parent instanceof XmlAttributeValue
                      && child instanceof XmlToken
                      && event.getOldChild() == null) {
                    // Just added attribute value
                    String text = child.getText();
                    // See if this is an attribute that takes a resource!
                    if (text.startsWith(PREFIX_RESOURCE_REF)) {
                      if (text.equals(PREFIX_RESOURCE_REF) || text.equals(ANDROID_PREFIX)) {
                        // Using code completion to insert resource reference; not yet done
                        return;
                      }
                      ResourceUrl url = ResourceUrl.parse(text);
                      if (url != null && url.name.isEmpty()) {
                        // Using code completion to insert resource reference; not yet done
                        return;
                      }
                    }
                  }
                  update(event);
                }
              }

              @Override
              public void childReplaced(@NotNull PsiTreeChangeEvent event) {
                myIgnoreChildrenChanged = true;
                if (isRelevant(event)) {
                  PsiElement child = event.getChild();
                  PsiElement parent = event.getParent();

                  if (parent instanceof XmlAttribute && child instanceof XmlToken) {
                    // Typing in attribute name. Don't need to do any rendering until there
                    // is an actual value
                    XmlAttributeValue valueElement = ((XmlAttribute) parent).getValueElement();
                    if (valueElement == null
                        || valueElement.getValue() == null
                        || valueElement.getValue().isEmpty()) {
                      return;
                    }
                  } else if (parent instanceof XmlAttributeValue
                      && child instanceof XmlToken
                      && event.getOldChild() != null) {
                    String newText = child.getText();
                    String prevText = event.getOldChild().getText();
                    // See if user is working on an incomplete URL, and is still not complete, e.g.
                    // typing in @string/foo manually
                    if (newText.startsWith(PREFIX_RESOURCE_REF)) {
                      ResourceUrl prevUrl = ResourceUrl.parse(prevText);
                      ResourceUrl newUrl = ResourceUrl.parse(newText);
                      if (prevUrl != null && prevUrl.name.isEmpty()) {
                        prevUrl = null;
                      }
                      if (newUrl != null && newUrl.name.isEmpty()) {
                        newUrl = null;
                      }
                      if (prevUrl == null && newUrl == null) {
                        return;
                      }
                    }
                  }
                  update(event);
                }
              }

              @Override
              public void childRemoved(@NotNull PsiTreeChangeEvent event) {
                myIgnoreChildrenChanged = true;
                if (isRelevant(event)) {
                  PsiElement child = event.getChild();
                  PsiElement parent = event.getParent();

                  if (parent instanceof XmlAttribute && child instanceof XmlToken) {
                    // Typing in attribute name. Don't need to do any rendering until there
                    // is an actual value
                    XmlAttributeValue valueElement = ((XmlAttribute) parent).getValueElement();
                    if (valueElement == null
                        || valueElement.getValue() == null
                        || valueElement.getValue().isEmpty()) {
                      return;
                    }
                  }
                  update(event);
                }
              }
            },
            project);

    ProjectBuilder.getInstance(project)
        .addAfterProjectBuildTask(
            new ProjectBuilder.AfterProjectBuildListener() {
              @Override
              protected void buildFinished() {
                if (myToolWindowForm != null && myToolWindowReady && !myToolWindowDisposed) {
                  ApplicationManager.getApplication()
                      .invokeLater(
                          new Runnable() {
                            @Override
                            public void run() {
                              render();
                            }
                          });
                }
              }
            });
  }
  private void installPropertiesChangeListeners() {
    final VirtualFileManager virtualFileManager = VirtualFileManager.getInstance();
    if (myVfsListener != null) {
      assert false;
      virtualFileManager.removeVirtualFileListener(myVfsListener);
    }
    myVfsListener =
        new VirtualFileAdapter() {
          @Override
          public void fileCreated(@NotNull VirtualFileEvent event) {
            if (PropertiesImplUtil.isPropertiesFile(event.getFile(), myProject)) {
              recreateEditorsPanel();
            }
          }

          @Override
          public void fileDeleted(@NotNull VirtualFileEvent event) {
            for (PropertiesFile file : myEditors.keySet()) {
              if (Comparing.equal(file.getVirtualFile(), event.getFile())) {
                recreateEditorsPanel();
                return;
              }
            }
          }

          @Override
          public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
            if (PropertiesImplUtil.isPropertiesFile(event.getFile(), myProject)) {
              if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
                recreateEditorsPanel();
              } else {
                updateEditorsFromProperties();
              }
            }
          }
        };

    virtualFileManager.addVirtualFileListener(myVfsListener, this);
    PsiTreeChangeAdapter psiTreeChangeAdapter =
        new PsiTreeChangeAdapter() {
          @Override
          public void childAdded(@NotNull PsiTreeChangeEvent event) {
            childrenChanged(event);
          }

          @Override
          public void childRemoved(@NotNull PsiTreeChangeEvent event) {
            childrenChanged(event);
          }

          @Override
          public void childReplaced(@NotNull PsiTreeChangeEvent event) {
            childrenChanged(event);
          }

          @Override
          public void childMoved(@NotNull PsiTreeChangeEvent event) {
            childrenChanged(event);
          }

          @Override
          public void childrenChanged(@NotNull PsiTreeChangeEvent event) {
            final PsiFile file = event.getFile();
            PropertiesFile propertiesFile = PropertiesImplUtil.getPropertiesFile(file);
            if (propertiesFile == null) return;
            if (!propertiesFile.getResourceBundle().equals(myResourceBundle)) return;
            updateEditorsFromProperties();
          }
        };
    PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiTreeChangeAdapter, this);
  }